from __future__ import annotations

import datetime as dt
from typing import Any, Dict, List, Optional

from fastapi import FastAPI, HTTPException
from sqlmodel import SQLModel, select

from catan.game import GameConfig
from catan.sdk import Action, ActionType, CatanEnv
from services.common.db import engine, session_scope
from services.common.schemas import ReplayArchive, ReplayDetail, ReplayMeta
from services.game.models import Game, GameEvent

app = FastAPI(title="Catan Analytics")


@app.on_event("startup")
def _startup() -> None:
    SQLModel.metadata.create_all(engine)


def _serialize_resources(resources: Dict[Any, int]) -> Dict[str, int]:
    return {str(getattr(res, "value", res)): int(val) for res, val in resources.items()}


def _serialize_observation(observation: Dict[str, Any]) -> Dict[str, Any]:
    game = dict(observation["game"])
    players = {}
    for name, info in game["players"].items():
        pdata = dict(info)
        if isinstance(pdata.get("resources"), dict):
            pdata["resources"] = _serialize_resources(pdata["resources"])
        players[name] = pdata
    game["players"] = players
    if "bank" in game:
        game["bank"] = _serialize_resources(game["bank"])
    return {"game": game, "board": observation["board"]}


def _load_game(game_id: str) -> Game:
    with session_scope() as session:
        game = session.get(Game, game_id)
        if not game:
            raise HTTPException(status_code=404, detail="Game not found")
        return game


def _load_events(game_id: str) -> List[GameEvent]:
    with session_scope() as session:
        return session.exec(
            select(GameEvent).where(GameEvent.game_id == game_id).order_by(GameEvent.idx)
        ).all()


def _replay_state(game: Game, events: List[GameEvent], step: int) -> Dict[str, Any]:
    slots = game.slots.get("slots", [])
    names = [slot.get("name") for slot in slots if slot.get("name")]
    colors = [slot.get("color", "player") for slot in slots if slot.get("name")]
    env = CatanEnv(GameConfig(player_names=names, colors=colors, seed=game.seed))
    applied_events = [event for event in events if event.applied]
    for event in applied_events[:step]:
        action = Action(ActionType(event.action_type), event.payload)
        env.step(action)
    return _serialize_observation(env.observe())


def _build_archive(game: Game, events: List[GameEvent]) -> ReplayArchive:
    slots = game.slots.get("slots", [])
    players = [slot.get("name") for slot in slots if slot.get("name")]
    actions = [
        {
            "idx": event.idx,
            "ts": event.ts,
            "actor": event.actor,
            "action": {"type": event.action_type, "payload": event.payload},
            "applied": event.applied,
            "meta": event.debug_payload or {},
        }
        for event in events
    ]
    return ReplayArchive(
        id=game.id,
        created_at=game.created_at,
        seed=game.seed,
        players=players,
        winner=game.winner,
        slots=slots,
        actions=actions,
    )


@app.get("/health")
def health() -> Dict[str, str]:
    return {"status": "ok"}


@app.get("/stats")
def stats(user: Optional[str] = None) -> Dict[str, Any]:
    with session_scope() as session:
        games = session.exec(select(Game)).all()
    total_games = len(games)
    finished_games = [g for g in games if g.status == "finished"]
    avg_turns = 0.0
    if finished_games:
        with session_scope() as session:
            counts = []
            for game in finished_games:
                count = session.exec(
                    select(GameEvent).where(GameEvent.game_id == game.id, GameEvent.applied == True)
                ).all()
                counts.append(len(count))
        avg_turns = sum(counts) / len(counts) if counts else 0.0
    if not user:
        return {
            "total_games": total_games,
            "finished_games": len(finished_games),
            "avg_turns": avg_turns,
        }
    user_games = [g for g in games if user in [slot.get("name") for slot in g.slots.get("slots", [])]]
    user_wins = sum(1 for g in games if g.winner == user)
    return {
        "total_games": total_games,
        "finished_games": len(finished_games),
        "avg_turns": avg_turns,
        "user_games": len(user_games),
        "user_wins": user_wins,
    }


@app.get("/replays")
def list_replays() -> Dict[str, Any]:
    with session_scope() as session:
        games = session.exec(select(Game).where(Game.status == "finished").order_by(Game.created_at.desc())).all()
    items: List[ReplayMeta] = []
    with session_scope() as session:
        for game in games:
            actions = session.exec(
                select(GameEvent).where(GameEvent.game_id == game.id, GameEvent.applied == True)
            ).all()
            players = [slot.get("name") for slot in game.slots.get("slots", []) if slot.get("name")]
            items.append(
                ReplayMeta(
                    id=game.id,
                    created_at=game.created_at,
                    players=players,
                    winner=game.winner,
                    total_actions=len(actions),
                )
            )
    return {"replays": [item.model_dump() for item in items]}


@app.get("/replays/{replay_id}")
def replay_detail(replay_id: str) -> Dict[str, Any]:
    game = _load_game(replay_id)
    events = _load_events(replay_id)
    applied = [event for event in events if event.applied]
    players = [slot.get("name") for slot in game.slots.get("slots", []) if slot.get("name")]
    actions = [
        {
            "idx": event.idx,
            "ts": event.ts,
            "actor": event.actor,
            "action": {"type": event.action_type, "payload": event.payload},
            "applied": event.applied,
            "meta": event.debug_payload or {},
        }
        for event in applied
    ]
    detail = ReplayDetail(
        id=game.id,
        created_at=game.created_at,
        seed=game.seed,
        players=players,
        winner=game.winner,
        total_actions=len(applied),
        actions=actions,
    )
    return detail.model_dump()


@app.get("/replays/{replay_id}/state")
def replay_state(replay_id: str, step: int = 0) -> Dict[str, Any]:
    game = _load_game(replay_id)
    events = _load_events(replay_id)
    state = _replay_state(game, events, step)
    return state


@app.get("/replays/{replay_id}/export")
def export_replay(replay_id: str) -> Dict[str, Any]:
    game = _load_game(replay_id)
    events = _load_events(replay_id)
    archive = _build_archive(game, events)
    return archive.model_dump()


@app.post("/replays/import")
def import_replay(payload: ReplayArchive) -> Dict[str, Any]:
    with session_scope() as session:
        existing = session.get(Game, payload.id)
        if existing:
            raise HTTPException(status_code=400, detail="Replay already exists")
        now = dt.datetime.now(dt.timezone.utc)
        game = Game(
            id=payload.id,
            name=f"Replay {payload.id[:8]}",
            status="finished",
            max_players=len(payload.slots) or len(payload.players),
            created_by="import",
            created_at=payload.created_at,
            updated_at=now,
            seed=payload.seed,
            slots={"slots": [slot.model_dump() for slot in payload.slots]},
            winner=payload.winner,
        )
        session.add(game)
        for action in payload.actions:
            event = GameEvent(
                game_id=payload.id,
                idx=action.idx,
                ts=action.ts,
                actor=action.actor,
                action_type=action.action.type,
                payload=action.action.payload,
                applied=action.applied,
                debug_payload=action.meta or None,
            )
            session.add(event)
    return {"status": "imported", "id": payload.id}
