from datetime import datetime, timezone

from fastapi import APIRouter, Depends, HTTPException, Response
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.boss_battle import BossBattle
from app.models.boss_pokemon import BossPokemon
from app.models.boss_result import BossResult
from app.models.game import Game
from app.models.nuzlocke_run import NuzlockeRun
from app.models.pokemon import Pokemon
from app.models.route import Route
from app.schemas.boss import (
    BossBattleCreate,
    BossBattleResponse,
    BossBattleUpdate,
    BossPokemonInput,
    BossReorderRequest,
    BossResultCreate,
    BossResultResponse,
)
from app.schemas.pokemon import BulkBossItem, BulkImportResult
from app.seeds.loader import upsert_bosses

router = APIRouter()


async def _get_version_group_id(session: AsyncSession, game_id: int) -> int:
    game = await session.get(Game, game_id)
    if game is None:
        raise HTTPException(status_code=404, detail="Game not found")
    if game.version_group_id is None:
        raise HTTPException(status_code=400, detail="Game has no version group assigned")
    return game.version_group_id


# --- Game-scoped (admin) endpoints ---


@router.get("/games/{game_id}/bosses", response_model=list[BossBattleResponse])
async def list_bosses(
    game_id: int, session: AsyncSession = Depends(get_session)
):
    vg_id = await _get_version_group_id(session, game_id)

    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.version_group_id == vg_id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
        .order_by(BossBattle.order)
    )
    return result.scalars().all()


@router.put("/games/{game_id}/bosses/reorder", response_model=list[BossBattleResponse])
async def reorder_bosses(
    game_id: int,
    data: BossReorderRequest,
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    boss_ids = [item.id for item in data.bosses]
    result = await session.execute(
        select(BossBattle).where(
            BossBattle.id.in_(boss_ids), BossBattle.version_group_id == vg_id
        )
    )
    bosses = {b.id: b for b in result.scalars().all()}

    if len(bosses) != len(boss_ids):
        raise HTTPException(status_code=400, detail="Some boss IDs not found in this game")

    # Phase 1: set temporary negative orders to avoid unique constraint violations
    for i, item in enumerate(data.bosses):
        bosses[item.id].order = -(i + 1)
    await session.flush()

    # Phase 2: set real orders
    for item in data.bosses:
        bosses[item.id].order = item.order
    await session.commit()

    # Re-fetch with eager loading
    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.version_group_id == vg_id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
        .order_by(BossBattle.order)
    )
    return result.scalars().all()


@router.post("/games/{game_id}/bosses", response_model=BossBattleResponse, status_code=201)
async def create_boss(
    game_id: int,
    data: BossBattleCreate,
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    boss = BossBattle(version_group_id=vg_id, **data.model_dump())
    session.add(boss)
    await session.commit()

    # Re-fetch with eager loading
    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.id == boss.id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
    )
    return result.scalar_one()


@router.put("/games/{game_id}/bosses/{boss_id}", response_model=BossBattleResponse)
async def update_boss(
    game_id: int,
    boss_id: int,
    data: BossBattleUpdate,
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.id == boss_id, BossBattle.version_group_id == vg_id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
    )
    boss = result.scalar_one_or_none()
    if boss is None:
        raise HTTPException(status_code=404, detail="Boss battle not found")

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

    await session.commit()
    await session.refresh(boss)

    # Re-fetch with eager loading
    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.id == boss.id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
    )
    return result.scalar_one()


@router.delete("/games/{game_id}/bosses/{boss_id}", status_code=204)
async def delete_boss(
    game_id: int,
    boss_id: int,
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    result = await session.execute(
        select(BossBattle).where(BossBattle.id == boss_id, BossBattle.version_group_id == vg_id)
    )
    boss = result.scalar_one_or_none()
    if boss is None:
        raise HTTPException(status_code=404, detail="Boss battle not found")

    await session.delete(boss)
    await session.commit()
    return Response(status_code=204)


@router.post("/games/{game_id}/bosses/bulk-import", response_model=BulkImportResult)
async def bulk_import_bosses(
    game_id: int,
    items: list[BulkBossItem],
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    # Build pokeapi_id -> id mapping
    result = await session.execute(select(Pokemon.pokeapi_id, Pokemon.id))
    dex_to_id = {row.pokeapi_id: row.id for row in result}

    # Build route name -> id mapping for after_route_name resolution
    result = await session.execute(
        select(Route.name, Route.id).where(Route.version_group_id == vg_id)
    )
    route_name_to_id = {row.name: row.id for row in result}

    bosses_data = [item.model_dump() for item in items]
    try:
        count = await upsert_bosses(session, vg_id, bosses_data, dex_to_id, route_name_to_id)
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Failed to import bosses: {e}")

    await session.commit()
    return BulkImportResult(created=count, updated=0, errors=[])


@router.put(
    "/games/{game_id}/bosses/{boss_id}/pokemon",
    response_model=BossBattleResponse,
)
async def set_boss_team(
    game_id: int,
    boss_id: int,
    team: list[BossPokemonInput],
    session: AsyncSession = Depends(get_session),
):
    vg_id = await _get_version_group_id(session, game_id)

    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.id == boss_id, BossBattle.version_group_id == vg_id)
        .options(selectinload(BossBattle.pokemon))
    )
    boss = result.scalar_one_or_none()
    if boss is None:
        raise HTTPException(status_code=404, detail="Boss battle not found")

    # Remove existing team
    for p in boss.pokemon:
        await session.delete(p)

    # Add new team
    for item in team:
        bp = BossPokemon(
            boss_battle_id=boss_id,
            pokemon_id=item.pokemon_id,
            level=item.level,
            order=item.order,
            condition_label=item.condition_label,
        )
        session.add(bp)

    await session.commit()

    # Clear identity map so selectinload fetches everything fresh
    # (expired Pokemon from deleted BossPokemon would otherwise cause
    # MissingGreenlet errors during response serialization)
    session.expunge_all()

    # Re-fetch with eager loading
    result = await session.execute(
        select(BossBattle)
        .where(BossBattle.id == boss.id)
        .options(selectinload(BossBattle.pokemon).selectinload(BossPokemon.pokemon))
    )
    return result.scalar_one()


# --- Run-scoped endpoints ---


@router.get("/runs/{run_id}/boss-results", response_model=list[BossResultResponse])
async def list_boss_results(
    run_id: int, session: AsyncSession = Depends(get_session)
):
    run = await session.get(NuzlockeRun, run_id)
    if run is None:
        raise HTTPException(status_code=404, detail="Run not found")

    result = await session.execute(
        select(BossResult)
        .where(BossResult.run_id == run_id)
        .order_by(BossResult.id)
    )
    return result.scalars().all()


@router.post("/runs/{run_id}/boss-results", response_model=BossResultResponse, status_code=201)
async def create_boss_result(
    run_id: int,
    data: BossResultCreate,
    session: AsyncSession = Depends(get_session),
):
    run = await session.get(NuzlockeRun, run_id)
    if run is None:
        raise HTTPException(status_code=404, detail="Run not found")

    boss = await session.get(BossBattle, data.boss_battle_id)
    if boss is None:
        raise HTTPException(status_code=404, detail="Boss battle not found")

    # Check for existing result (upsert)
    existing = await session.execute(
        select(BossResult).where(
            BossResult.run_id == run_id,
            BossResult.boss_battle_id == data.boss_battle_id,
        )
    )
    result = existing.scalar_one_or_none()

    if result:
        result.result = data.result
        result.attempts = data.attempts
        result.completed_at = datetime.now(timezone.utc) if data.result == "won" else None
    else:
        result = BossResult(
            run_id=run_id,
            boss_battle_id=data.boss_battle_id,
            result=data.result,
            attempts=data.attempts,
            completed_at=datetime.now(timezone.utc) if data.result == "won" else None,
        )
        session.add(result)

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


@router.delete("/runs/{run_id}/boss-results/{result_id}", status_code=204)
async def delete_boss_result(
    run_id: int,
    result_id: int,
    session: AsyncSession = Depends(get_session),
):
    result = await session.execute(
        select(BossResult).where(
            BossResult.id == result_id, BossResult.run_id == run_id
        )
    )
    boss_result = result.scalar_one_or_none()
    if boss_result is None:
        raise HTTPException(status_code=404, detail="Boss result not found")

    await session.delete(boss_result)
    await session.commit()
    return Response(status_code=204)
