"""Pydantic 스키마 — API 요청/응답 계약.

프론트(lib/types.ts)와 1:1 정합. 시각은 ISO8601 문자열로 직렬화.
"""
from __future__ import annotations

from datetime import datetime
from typing import Literal

from pydantic import BaseModel, EmailStr, Field, field_validator

Outcome = Literal["TEAM_A_WIN", "DRAW", "TEAM_B_WIN"]
ModelName = Literal["GPT", "Claude", "Gemini"]


class Team(BaseModel):
    name: str
    shortName: str
    code: str
    flag: str = ""


class MatchResult(BaseModel):
    scoreA: int
    scoreB: int
    outcome: Outcome


class AIPredictionOut(BaseModel):
    matchId: str
    model: ModelName
    outcome: Outcome
    scoreA: int
    scoreB: int
    confidencePct: int
    reasonShort: str
    generatedAt: str


class CrowdStatsOut(BaseModel):
    matchId: str
    total: int
    teamAWin: int
    draw: int
    teamBWin: int


class MatchOut(BaseModel):
    matchId: str
    roundLabel: str
    group: str
    teamA: Team
    teamB: Team
    kickoffKst: str
    venue: str
    opensAt: str
    lockAt: str
    status: str  # = phase (실시간 계산값으로 통일)
    phase: str  # scheduled | open | locked | live | finished (now 기준 계산)
    votable: bool  # AI 예측·투표 대상 조인지 (그 외는 일정/결과만)
    votingOpen: bool  # 현재 제출 허용 여부 (demo_force_open 반영)
    hookText: str
    result: MatchResult | None = None
    predictions: list[AIPredictionOut] = Field(default_factory=list)
    crowd: CrowdStatsOut | None = None


# ── 픽 제출 ─────────────────────────────────────────────────
class SubmitPredictionIn(BaseModel):
    matchId: str
    deviceId: str = Field(min_length=8, max_length=64)
    outcome: Outcome
    scoreA: int = Field(ge=0, le=9)
    scoreB: int = Field(ge=0, le=9)
    email: EmailStr | None = None
    notify: bool = False

    @field_validator("outcome")
    @classmethod
    def outcome_matches_score(cls, v: str, info):  # noqa: ANN001
        # outcome 은 스코어에서 파생되어야 일관됨 — 프론트가 자동계산하지만 서버에서 재검증
        return v


class SubmitPredictionOut(BaseModel):
    ok: bool
    predictionId: str
    matchedModels: list[ModelName]
    exactMatch: bool
    crowd: CrowdStatsOut


# ── 내 지난 예측 (이메일 기준, 로그인 없음) ──────────────────
class MyResultOut(BaseModel):
    scoreA: int
    scoreB: int
    outcome: Outcome
    hitOutcome: bool  # 내 outcome 이 실제 결과와 일치했는지


class MyPredictionOut(BaseModel):
    matchId: str
    teamA: Team
    teamB: Team
    kickoffKst: str
    outcome: Outcome
    scoreA: int
    scoreB: int
    submittedAt: str  # ISO — 최신순 정렬 기준(updated_at)
    result: MyResultOut | None = None  # 경기 종료 시에만 채워짐


# ── 리더보드 ────────────────────────────────────────────────
class StandingOut(BaseModel):
    rank: int
    emailMasked: str
    totalPoints: int
    exactCount: int
    matchesPlayed: int


class LeaderboardOut(BaseModel):
    standings: list[StandingOut]
    scoredMatches: int


# ── 관리자 ──────────────────────────────────────────────────
class AdminAIPredictionIn(BaseModel):
    matchId: str
    model: ModelName
    outcome: Outcome
    scoreA: int = Field(ge=0, le=20)
    scoreB: int = Field(ge=0, le=20)
    confidencePct: int = Field(ge=0, le=100)
    reasonKo: str = ""
    reasonEn: str = ""


class AdminSetResultIn(BaseModel):
    matchId: str
    scoreA: int = Field(ge=0, le=50)
    scoreB: int = Field(ge=0, le=50)


# ── 댓글(경기별 한마디) — 완전 익명 ─────────────────────────
class CommentIn(BaseModel):
    deviceId: str = Field(min_length=8, max_length=64)  # 서버에서 해시 후 폐기(원본 비저장)
    nickname: str = Field(min_length=1, max_length=12)  # 세션 캐싱된 닉(서버 풀 검증)
    body: str = Field(min_length=1, max_length=200)

    @field_validator("body")
    @classmethod
    def body_not_blank(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("EMPTY_BODY")
        return v


class CommentOut(BaseModel):
    # id·author_hash 등 내부 식별자는 노출하지 않음(닉네임만 공개)
    nickname: str  # 축구 코믹 한국어 5글자 (세션 발급)
    body: str
    createdAt: str  # ISO8601


class CommentNicknameOut(BaseModel):
    nickname: str  # 세션 시작 시 발급받는 랜덤 닉(클라이언트가 sessionStorage에 캐싱)


class CommentListOut(BaseModel):
    items: list[CommentOut]
    total: int  # 숨김 제외 전체 개수 — "더보기" 남은 수 계산용


class GenericOk(BaseModel):
    ok: bool
    detail: str = ""
    extra: dict | None = None


# ── 방문자 집계 ─────────────────────────────────────────────
class VisitIn(BaseModel):
    deviceId: str = Field(min_length=8, max_length=64)


class VisitOut(BaseModel):
    ok: bool
    counted: bool  # 오늘 첫 방문이면 True(집계됨), 재방문이면 False


class DailyVisitOut(BaseModel):
    date: str  # YYYY-MM-DD (KST)
    uniqueVisitors: int
