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

from app.core.database import get_session
from app.models.game import Game
from app.models.route import Route
from app.models.route_encounter import RouteEncounter
from app.schemas.game import (
    GameCreate,
    GameDetailResponse,
    GameResponse,
    GameUpdate,
    RouteCreate,
    RouteReorderRequest,
    RouteResponse,
    RouteUpdate,
    RouteWithChildrenResponse,
)

router = APIRouter()


@router.get("", response_model=list[GameResponse])
async def list_games(session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(Game).order_by(Game.id))
    return result.scalars().all()


@router.get("/{game_id}", response_model=GameDetailResponse)
async def get_game(game_id: int, session: AsyncSession = Depends(get_session)):
    result = await session.execute(
        select(Game)
        .where(Game.id == game_id)
        .options(selectinload(Game.routes))
    )
    game = result.scalar_one_or_none()
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    # Sort routes by order for the response
    game.routes.sort(key=lambda r: r.order)
    return game


@router.get(
    "/{game_id}/routes",
    response_model=list[RouteWithChildrenResponse] | list[RouteResponse],
)
async def list_game_routes(
    game_id: int,
    flat: bool = False,
    session: AsyncSession = Depends(get_session),
):
    """
    List routes for a game.

    By default, returns a hierarchical structure with top-level routes containing
    nested children. Use `flat=True` to get a flat list of all routes.
    """
    # Verify game exists
    game = await session.get(Game, game_id)
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    result = await session.execute(
        select(Route)
        .where(Route.game_id == game_id)
        .options(selectinload(Route.route_encounters))
        .order_by(Route.order)
    )
    all_routes = result.scalars().all()

    def route_to_dict(route: Route) -> dict:
        methods = sorted({re.encounter_method for re in route.route_encounters})
        return {
            "id": route.id,
            "name": route.name,
            "game_id": route.game_id,
            "order": route.order,
            "parent_route_id": route.parent_route_id,
            "encounter_methods": methods,
        }

    if flat:
        return [route_to_dict(r) for r in all_routes]

    # Build hierarchical structure
    # Group children by parent_route_id
    children_by_parent: dict[int, list[dict]] = {}
    top_level_routes: list[Route] = []

    for route in all_routes:
        if route.parent_route_id is None:
            top_level_routes.append(route)
        else:
            children_by_parent.setdefault(route.parent_route_id, []).append(
                route_to_dict(route)
            )

    # Build response with nested children
    response = []
    for route in top_level_routes:
        route_dict = route_to_dict(route)
        route_dict["children"] = children_by_parent.get(route.id, [])
        response.append(route_dict)

    return response


# --- Admin endpoints ---


@router.post("", response_model=GameResponse, status_code=201)
async def create_game(
    data: GameCreate, session: AsyncSession = Depends(get_session)
):
    existing = await session.execute(
        select(Game).where(Game.slug == data.slug)
    )
    if existing.scalar_one_or_none() is not None:
        raise HTTPException(status_code=409, detail="Game with this slug already exists")

    game = Game(**data.model_dump())
    session.add(game)
    await session.commit()
    await session.refresh(game)
    return game


@router.put("/{game_id}", response_model=GameResponse)
async def update_game(
    game_id: int, data: GameUpdate, session: AsyncSession = Depends(get_session)
):
    game = await session.get(Game, game_id)
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    update_data = data.model_dump(exclude_unset=True)
    if "slug" in update_data:
        existing = await session.execute(
            select(Game).where(Game.slug == update_data["slug"], Game.id != game_id)
        )
        if existing.scalar_one_or_none() is not None:
            raise HTTPException(status_code=409, detail="Game with this slug already exists")

    for field, value in update_data.items():
        setattr(game, field, value)

    await session.commit()
    await session.refresh(game)
    return game


@router.delete("/{game_id}", status_code=204)
async def delete_game(
    game_id: int, session: AsyncSession = Depends(get_session)
):
    result = await session.execute(
        select(Game).where(Game.id == game_id).options(selectinload(Game.runs))
    )
    game = result.scalar_one_or_none()
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    if game.runs:
        raise HTTPException(
            status_code=409,
            detail="Cannot delete game with existing runs. Delete the runs first.",
        )

    # Delete routes (and their route_encounters via cascade)
    routes = await session.execute(
        select(Route).where(Route.game_id == game_id)
    )
    for route in routes.scalars().all():
        await session.delete(route)

    await session.delete(game)
    await session.commit()


@router.post("/{game_id}/routes", response_model=RouteResponse, status_code=201)
async def create_route(
    game_id: int, data: RouteCreate, session: AsyncSession = Depends(get_session)
):
    game = await session.get(Game, game_id)
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    route = Route(game_id=game_id, **data.model_dump())
    session.add(route)
    await session.commit()
    await session.refresh(route)
    return route


@router.put("/{game_id}/routes/reorder", response_model=list[RouteResponse])
async def reorder_routes(
    game_id: int,
    data: RouteReorderRequest,
    session: AsyncSession = Depends(get_session),
):
    game = await session.get(Game, game_id)
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")

    for item in data.routes:
        route = await session.get(Route, item.id)
        if route is None or route.game_id != game_id:
            raise HTTPException(
                status_code=400,
                detail=f"Route {item.id} not found in this game",
            )
        route.order = item.order

    await session.commit()

    result = await session.execute(
        select(Route).where(Route.game_id == game_id).order_by(Route.order)
    )
    return result.scalars().all()


@router.put("/{game_id}/routes/{route_id}", response_model=RouteResponse)
async def update_route(
    game_id: int,
    route_id: int,
    data: RouteUpdate,
    session: AsyncSession = Depends(get_session),
):
    route = await session.get(Route, route_id)
    if route is None or route.game_id != game_id:
        raise HTTPException(status_code=404, detail="Route not found in this game")

    for field, value in data.model_dump(exclude_unset=True).items():
        setattr(route, field, value)

    await session.commit()
    await session.refresh(route)
    return route


@router.delete("/{game_id}/routes/{route_id}", status_code=204)
async def delete_route(
    game_id: int,
    route_id: int,
    session: AsyncSession = Depends(get_session),
):
    result = await session.execute(
        select(Route)
        .where(Route.id == route_id, Route.game_id == game_id)
        .options(selectinload(Route.encounters))
    )
    route = result.scalar_one_or_none()
    if route is None:
        raise HTTPException(status_code=404, detail="Route not found in this game")

    if route.encounters:
        raise HTTPException(
            status_code=409,
            detail="Cannot delete route with existing encounters. Delete the encounters first.",
        )

    await session.delete(route)
    await session.commit()
