"""API сада (ТЗ 3.13): сцена, монеты, магазин декораций и расширений."""

from typing import Any

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from sqlalchemy import select

from app.dependencies import DbDep, UserDep
from app.models import GardenItem, Task, XpEvent
from app.services import garden
from app.services.garden import (
    DECORATIONS,
    EXPANSIONS,
    MAX_STAGE,
    UPGRADE_COSTS,
    balance,
    ensure_garden,
    grid_size,
    house_footprint,
    next_expansion,
    species_catalog,
)
from app.services.xp import level_from_xp, total_xp

router = APIRouter(prefix="/api/garden", tags=["garden"])


class MoveIn(BaseModel):
    """Позиция элемента: x/y None — вернуть в инвентарь (не размещено)."""

    x: int | None = None
    y: int | None = None


class BuyIn(BaseModel):
    item_key: str


def _state(db: Any) -> dict[str, Any]:
    """Полное состояние сцены после ленивого обслуживания."""
    ensure_garden(db)
    level = level_from_xp(total_xp(db))
    size = grid_size(db)

    # Растения + их события (редкость, задача) одним запросом
    rows = db.execute(
        select(GardenItem, XpEvent, Task)
        .join(XpEvent, GardenItem.ref_id == XpEvent.id, isouter=True)
        .join(Task, XpEvent.task_id == Task.id, isouter=True)
        .where(GardenItem.kind == "plant")
        .order_by(GardenItem.id)
    ).all()
    items: list[dict[str, Any]] = []
    for item, event, task in rows:
        items.append(
            {
                "id": item.id,
                "kind": "plant",
                "ref_id": item.ref_id,
                "item_key": item.item_key,
                "x": item.x,
                "y": item.y,
                "stage": item.stage,
                "rarity": event.rarity if event else "common",
                "task_title": task.title if task else None,
                "done_at": event.created_at.isoformat() if event else None,
            }
        )
    decorations = db.scalars(
        select(GardenItem).where(GardenItem.kind == "decoration").order_by(GardenItem.id)
    ).all()
    items += [
        {
            "id": d.id,
            "kind": "decoration",
            "ref_id": None,
            "item_key": d.item_key,
            "x": d.x,
            "y": d.y,
            "stage": 0,
            "rarity": None,
            "task_title": None,
            "done_at": None,
        }
        for d in decorations
    ]

    expansion = next_expansion(db)
    return {
        "balance": balance(db),
        "level": level,
        "grid": size,
        "items": items,
        "decorations": [
            {"key": key, "cost": spec["cost"], "unique": spec["unique"]}
            for key, spec in DECORATIONS.items()
        ],
        "next_expansion": expansion,
        "expansions_bought": garden.expansions_bought(db),
        "species": species_catalog(level),
        "upgrade_costs": UPGRADE_COSTS,
    }


@router.get("")
async def get_garden(db: DbDep, user: UserDep) -> dict[str, Any]:
    """Состояние сцены: баланс, элементы, каталоги, сетка."""
    return _state(db)


@router.patch("/items/{item_id}")
async def move_item(item_id: int, move: MoveIn, db: DbDep, user: UserDep) -> dict[str, Any]:
    item = db.get(GardenItem, item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Item not found")
    # x/y None — декорация возвращается в инвентарь (растениям нельзя: они от задач)
    if move.x is None or move.y is None:
        if item.kind != "decoration":
            raise HTTPException(status_code=400, detail="Plants cannot be stored")
        item.x, item.y = None, None
        db.flush()
        return {"ok": True}
    size = grid_size(db)
    if not (0 <= move.x < size["cols"] and 0 <= move.y < size["rows"]):
        raise HTTPException(status_code=400, detail="Position outside the garden")
    if item.kind == "plant" and (move.x, move.y) in house_footprint(
        size["cols"], size["rows"]
    ):
        raise HTTPException(status_code=400, detail="Cannot place on the house")
    item.x, item.y = move.x, move.y
    db.flush()
    return {"ok": True}


@router.post("/plants/{item_id}/upgrade")
async def upgrade_plant(item_id: int, db: DbDep, user: UserDep) -> dict[str, Any]:
    item = db.get(GardenItem, item_id)
    if item is None or item.kind != "plant":
        raise HTTPException(status_code=404, detail="Plant not found")
    if item.stage >= MAX_STAGE:
        raise HTTPException(status_code=400, detail="Plant is fully grown")
    event = db.get(XpEvent, item.ref_id) if item.ref_id else None
    rarity = event.rarity if event else "common"
    cost = UPGRADE_COSTS[rarity][item.stage]
    if not garden.can_afford(db, cost):
        raise HTTPException(
            status_code=402, detail=f"Not enough coins: need {cost}"
        )
    garden.spend(db, cost, f"upgrade:{item.item_key}")
    item.stage += 1
    db.flush()
    return {"ok": True, "stage": item.stage, "cost": cost, "balance": balance(db)}


@router.post("/shop/buy")
async def shop_buy(buy: BuyIn, db: DbDep, user: UserDep) -> dict[str, Any]:
    item_key = buy.item_key
    level = level_from_xp(total_xp(db))

    if item_key in EXPANSIONS:
        expansion = next_expansion(db)
        if expansion is None or expansion["key"] != item_key:
            raise HTTPException(status_code=409, detail="Expansion already bought")
        spec = EXPANSIONS[item_key]
        if level < spec["level"]:
            raise HTTPException(
                status_code=403, detail=f"Unlocks at level {spec['level']}"
            )
        if not garden.can_afford(db, spec["cost"]):
            raise HTTPException(status_code=402, detail=f"Not enough coins: need {spec['cost']}")
        garden.spend(db, spec["cost"], item_key)
        db.flush()
        return {"ok": True, "grid": grid_size(db), "balance": balance(db)}

    if item_key in DECORATIONS:
        spec = DECORATIONS[item_key]
        owned = db.scalar(
            select(GardenItem).where(
                GardenItem.kind == "decoration", GardenItem.item_key == item_key
            )
        )
        if spec["unique"] and owned is not None:
            raise HTTPException(status_code=409, detail="Already bought")
        if not garden.can_afford(db, spec["cost"]):
            raise HTTPException(status_code=402, detail=f"Not enough coins: need {spec['cost']}")
        # Покупка кладёт декорацию в инвентарь (x/y NULL) — пользователь сам
        # перетаскивает её на карту, место покупки всегда видно
        item = GardenItem(kind="decoration", item_key=item_key, x=None, y=None)
        db.add(item)
        garden.spend(db, spec["cost"], item_key)
        db.flush()
        return {
            "ok": True,
            "item": {"id": item.id, "item_key": item.item_key, "x": item.x, "y": item.y},
            "balance": balance(db),
        }

    raise HTTPException(status_code=400, detail=f"Unknown item: {item_key}")
