"""픽 제출 API — 검증 · 중복방지(1회 수정) · crowd 원자적 증분 · 매칭 모델 계산."""
from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from ..database import get_db
from ..domain import (
    compute_phase,
    crowd_out,
    is_open_for_voting,
    is_votable,
    my_prediction_out,
)
from ..models import AIPrediction, CrowdStats, Match, UserPrediction
from ..scoring import outcome_of
from ..schemas import MyPredictionOut, SubmitPredictionIn, SubmitPredictionOut

router = APIRouter(prefix="/api/predictions", tags=["predictions"])

_COL = {"TEAM_A_WIN": "team_a_win", "DRAW": "draw", "TEAM_B_WIN": "team_b_win"}


async def _adjust_crowd(
    db: AsyncSession, match_id: str, *, add: str | None, remove: str | None
) -> None:
    """crowd_stats 원자적 증분/보정 (Postgres UPDATE)."""
    values: dict = {}
    if add:
        col = _COL[add]
        values[col] = CrowdStats.__table__.c[col] + 1
        if not remove:  # 신규 제출이면 total +1
            values["total"] = CrowdStats.total + 1
    if remove and remove != add:
        col = _COL[remove]
        values[col] = CrowdStats.__table__.c[col] - 1
    if values:
        await db.execute(
            update(CrowdStats).where(CrowdStats.match_id == match_id).values(**values)
        )


@router.get("/mine", response_model=list[MyPredictionOut])
async def my_predictions(
    email: str, db: AsyncSession = Depends(get_db)
) -> list[MyPredictionOut]:
    """이메일 기준 내 지난 예측 목록 (최신 제출순). 로그인 없음 — 이메일이 신원."""
    e = email.strip().lower()
    if not e:
        return []
    rows = (
        await db.execute(
            select(UserPrediction, Match)
            .join(Match, Match.match_id == UserPrediction.match_id)
            .where(func.lower(UserPrediction.email) == e)
            .order_by(UserPrediction.updated_at.desc())
        )
    ).all()
    return [my_prediction_out(up, m) for up, m in rows]


@router.post("", response_model=SubmitPredictionOut)
async def submit_prediction(
    body: SubmitPredictionIn, db: AsyncSession = Depends(get_db)
) -> SubmitPredictionOut:
    match = (
        await db.execute(
            select(Match)
            .where(Match.match_id == body.matchId)
            .options(selectinload(Match.predictions))
        )
    ).scalars().first()
    if not match:
        raise HTTPException(status_code=404, detail="MATCH_NOT_FOUND")
    if not is_votable(match):
        raise HTTPException(status_code=403, detail="MATCH_NOT_VOTABLE")
    if not is_open_for_voting(match):
        # 종료 사유 정밀 구분: 종료 / 경기중 / 투표종료 / 오픈전
        phase = compute_phase(match)
        detail = {
            "finished": "MATCH_FINISHED",  # 결과 입력됨
            "live": "MATCH_LIVE",  # 경기중
            "locked": "MATCH_LOCKED",  # 투표 종료(킥오프 1h 전)
            "scheduled": "MATCH_NOT_OPEN",  # 아직 오픈 전
        }.get(phase, "MATCH_LOCKED")
        code = 409 if phase == "finished" else 423
        raise HTTPException(status_code=code, detail=detail)

    # outcome 은 스코어에서 도출 (입력과 불일치해도 스코어 기준으로 정규화)
    outcome = outcome_of(body.scoreA, body.scoreB)
    email = str(body.email).strip().lower() if body.email else None

    # 중복 식별: ① 이메일(있으면 1차 신원 — 다른 기기여도 동일인) ② 기기(deviceId)
    existing = None
    if email:
        existing = (
            await db.execute(
                select(UserPrediction).where(
                    UserPrediction.match_id == body.matchId,
                    func.lower(UserPrediction.email) == email,
                )
            )
        ).scalars().first()
    if existing is None:
        existing = (
            await db.execute(
                select(UserPrediction).where(
                    UserPrediction.match_id == body.matchId,
                    UserPrediction.device_id == body.deviceId,
                )
            )
        ).scalars().first()

    if existing:
        # 마감 전 1회 수정: 분포 보정 (이전 outcome 제거, 새 outcome 추가)
        await _adjust_crowd(db, body.matchId, add=outcome, remove=existing.outcome)
        existing.outcome = outcome
        existing.score_a = body.scoreA
        existing.score_b = body.scoreB
        existing.device_id = body.deviceId  # 최신 제출 기기로 갱신
        if email:
            existing.email = email
        existing.notify = body.notify
        pred = existing
    else:
        await _adjust_crowd(db, body.matchId, add=outcome, remove=None)
        pred = UserPrediction(
            match_id=body.matchId,
            device_id=body.deviceId,
            outcome=outcome,
            score_a=body.scoreA,
            score_b=body.scoreB,
            email=email,
            notify=body.notify,
        )
        db.add(pred)

    await db.commit()

    # 매칭 AI 모델 (같은 outcome) + 정확스코어 일치 여부
    matched: list[str] = []
    exact = False
    for p in match.predictions:
        if p.outcome == outcome:
            matched.append(p.model)
            if p.score_a == body.scoreA and p.score_b == body.scoreB:
                exact = True

    crowd = (
        await db.execute(
            select(CrowdStats).where(CrowdStats.match_id == body.matchId)
        )
    ).scalars().first()

    return SubmitPredictionOut(
        ok=True,
        predictionId=f"{body.matchId}_{body.deviceId}",
        matchedModels=matched,  # type: ignore[arg-type]
        exactMatch=exact,
        crowd=crowd_out(crowd, body.matchId),
    )
