from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload

from app.core.database import get_session
from app.models.evolution import Evolution
from app.models.pokemon import Pokemon
from app.schemas.pokemon import (
    BulkEvolutionItem,
    BulkImportResult,
    EvolutionAdminResponse,
    EvolutionCreate,
    EvolutionUpdate,
    PaginatedEvolutionResponse,
)

router = APIRouter()


@router.get("/evolutions", response_model=PaginatedEvolutionResponse)
async def list_evolutions(
    search: str | None = Query(None),
    trigger: str | None = Query(None),
    limit: int = Query(50, ge=1, le=500),
    offset: int = Query(0, ge=0),
    session: AsyncSession = Depends(get_session),
):
    base_query = (
        select(Evolution)
        .options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
    )

    if search:
        search_lower = search.lower()
        # Join pokemon to search by name
        from_pokemon = select(Pokemon.id).where(
            func.lower(Pokemon.name).contains(search_lower)
        ).scalar_subquery()
        base_query = base_query.where(
            or_(
                Evolution.from_pokemon_id.in_(from_pokemon),
                Evolution.to_pokemon_id.in_(from_pokemon),
                func.lower(Evolution.trigger).contains(search_lower),
                func.lower(Evolution.item).contains(search_lower),
            )
        )
    if trigger:
        base_query = base_query.where(Evolution.trigger == trigger)

    # Count total (without eager loads)
    count_base = select(Evolution)
    if search:
        search_lower = search.lower()
        from_pokemon = select(Pokemon.id).where(
            func.lower(Pokemon.name).contains(search_lower)
        ).scalar_subquery()
        count_base = count_base.where(
            or_(
                Evolution.from_pokemon_id.in_(from_pokemon),
                Evolution.to_pokemon_id.in_(from_pokemon),
                func.lower(Evolution.trigger).contains(search_lower),
                func.lower(Evolution.item).contains(search_lower),
            )
        )
    if trigger:
        count_base = count_base.where(Evolution.trigger == trigger)
    count_query = select(func.count()).select_from(count_base.subquery())
    total = (await session.execute(count_query)).scalar() or 0

    items_query = base_query.order_by(Evolution.from_pokemon_id, Evolution.to_pokemon_id).offset(offset).limit(limit)
    result = await session.execute(items_query)
    items = result.scalars().unique().all()

    return PaginatedEvolutionResponse(
        items=items,
        total=total,
        limit=limit,
        offset=offset,
    )


@router.post("/evolutions", response_model=EvolutionAdminResponse, status_code=201)
async def create_evolution(
    data: EvolutionCreate, session: AsyncSession = Depends(get_session)
):
    from_pokemon = await session.get(Pokemon, data.from_pokemon_id)
    if from_pokemon is None:
        raise HTTPException(status_code=404, detail="From pokemon not found")

    to_pokemon = await session.get(Pokemon, data.to_pokemon_id)
    if to_pokemon is None:
        raise HTTPException(status_code=404, detail="To pokemon not found")

    evolution = Evolution(**data.model_dump())
    session.add(evolution)
    await session.commit()

    # Reload with relationships
    result = await session.execute(
        select(Evolution)
        .where(Evolution.id == evolution.id)
        .options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
    )
    return result.scalar_one()


@router.put("/evolutions/{evolution_id}", response_model=EvolutionAdminResponse)
async def update_evolution(
    evolution_id: int,
    data: EvolutionUpdate,
    session: AsyncSession = Depends(get_session),
):
    evolution = await session.get(Evolution, evolution_id)
    if evolution is None:
        raise HTTPException(status_code=404, detail="Evolution not found")

    update_data = data.model_dump(exclude_unset=True)

    if "from_pokemon_id" in update_data:
        from_pokemon = await session.get(Pokemon, update_data["from_pokemon_id"])
        if from_pokemon is None:
            raise HTTPException(status_code=404, detail="From pokemon not found")

    if "to_pokemon_id" in update_data:
        to_pokemon = await session.get(Pokemon, update_data["to_pokemon_id"])
        if to_pokemon is None:
            raise HTTPException(status_code=404, detail="To pokemon not found")

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

    await session.commit()

    # Reload with relationships
    result = await session.execute(
        select(Evolution)
        .where(Evolution.id == evolution.id)
        .options(joinedload(Evolution.from_pokemon), joinedload(Evolution.to_pokemon))
    )
    return result.scalar_one()


@router.delete("/evolutions/{evolution_id}", status_code=204)
async def delete_evolution(
    evolution_id: int, session: AsyncSession = Depends(get_session)
):
    evolution = await session.get(Evolution, evolution_id)
    if evolution is None:
        raise HTTPException(status_code=404, detail="Evolution not found")

    await session.delete(evolution)
    await session.commit()


@router.post("/evolutions/bulk-import", response_model=BulkImportResult)
async def bulk_import_evolutions(
    items: list[BulkEvolutionItem],
    session: AsyncSession = Depends(get_session),
):
    # 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}

    created = 0
    updated = 0
    errors: list[str] = []

    for item in items:
        from_id = dex_to_id.get(item.from_pokeapi_id)
        to_id = dex_to_id.get(item.to_pokeapi_id)

        if from_id is None:
            errors.append(f"Pokemon with pokeapi_id {item.from_pokeapi_id} not found")
            continue
        if to_id is None:
            errors.append(f"Pokemon with pokeapi_id {item.to_pokeapi_id} not found")
            continue

        try:
            # Check if evolution already exists
            existing = await session.execute(
                select(Evolution).where(
                    Evolution.from_pokemon_id == from_id,
                    Evolution.to_pokemon_id == to_id,
                )
            )
            evolution = existing.scalar_one_or_none()

            if evolution is not None:
                evolution.trigger = item.trigger
                evolution.min_level = item.min_level
                evolution.item = item.item
                evolution.held_item = item.held_item
                evolution.condition = item.condition
                evolution.region = item.region
                updated += 1
            else:
                evolution = Evolution(
                    from_pokemon_id=from_id,
                    to_pokemon_id=to_id,
                    trigger=item.trigger,
                    min_level=item.min_level,
                    item=item.item,
                    held_item=item.held_item,
                    condition=item.condition,
                    region=item.region,
                )
                session.add(evolution)
                created += 1
        except Exception as e:
            errors.append(f"Evolution {item.from_pokeapi_id} -> {item.to_pokeapi_id}: {e}")

    await session.commit()
    return BulkImportResult(created=created, updated=updated, errors=errors)
