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

Принцип «контент открывается за опыт, покупается за монеты»: опыт (уровень)
открывает виды растений и землю, монеты покупают апгрейды, декорации и
расширения карты. Монеты — валюта сада, отдельная от XP: опыт только растёт,
монеты тратятся.
"""

from typing import Any

from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.models import CoinEvent, GardenItem, XpEvent
from app.services.xp import level_from_xp, total_xp

# --- Экономика ---

# Монеты за задачу: floor(xp / 2)
COINS_PER_XP = 2

# Бонус за достижение уровня L (одним событием при пересечении границы)
LEVEL_BONUS = 25


def coins_for_xp(xp: int) -> int:
    return xp // COINS_PER_XP


def level_bonus_coins(level: int) -> int:
    return LEVEL_BONUS * level


# Апгрейд растения по стадиям (0→1, 1→2), цена по редкости
UPGRADE_COSTS: dict[str, tuple[int, int]] = {
    "common": (20, 50),
    "rare": (40, 100),
    "epic": (80, 200),
}
MAX_STAGE = 2

# Каталог декораций: ключ -> (цена, уникальная). Только косметика — без бонусов.
DECORATIONS: dict[str, dict[str, Any]] = {
    "fence": {"cost": 30, "unique": False},
    "flowerbed": {"cost": 40, "unique": False},
    "lantern": {"cost": 80, "unique": True},
    "bench": {"cost": 90, "unique": True},
    "birdbath": {"cost": 120, "unique": True},
    "pond": {"cost": 250, "unique": True},
    "gazebo": {"cost": 400, "unique": True},
}

# Расширения карты: ключ -> (уровень открытия, цена). Каждое добавляет
# EXPANSION_PERIMETER клеток и к ширине, и к высоте; старт 32×20.
BASE_COLS = 32
BASE_ROWS = 20
EXPANSION_PERIMETER = 8
EXPANSIONS: dict[str, dict[str, int]] = {
    "expansion_1": {"level": 3, "cost": 100},
    "expansion_2": {"level": 5, "cost": 250},
    "expansion_3": {"level": 8, "cost": 500},
}

# Виды растений: открываются уровнем («контент за опыт»); premium — только
# для rare/epic закрытий («элитные виды приживаются у опытного садовника»).
# Ключ = phosphor-иконка кита (проверено по icons.css).
SPECIES: list[dict[str, Any]] = [
    {"key": "ph-flower", "level": 1, "premium": False},
    {"key": "ph-leaf", "level": 1, "premium": False},
    {"key": "ph-tree-evergreen", "level": 1, "premium": False},
    {"key": "ph-flower-lotus", "level": 2, "premium": False},
    {"key": "ph-cactus", "level": 4, "premium": True},
    {"key": "ph-tree", "level": 6, "premium": False},
]

# Домик в центре сетки: footprint, который не занимают растения (ячейки)
HOUSE_COLS = 4
HOUSE_ROWS = 3

# Спираль авторазмещения: снаружи от footprint домика
SPIRAL_START_RADIUS = 3


def balance(db: Session) -> int:
    total = db.scalar(select(func.coalesce(func.sum(CoinEvent.amount), 0)))
    return int(total or 0)


def expansions_bought(db: Session) -> int:
    return int(
        db.scalar(
            select(func.count())
            .select_from(CoinEvent)
            .where(CoinEvent.source == "shop", CoinEvent.item_key.like("expansion_%"))
        )
        or 0
    )


def grid_size(db: Session) -> dict[str, int]:
    extra = expansions_bought(db) * EXPANSION_PERIMETER
    return {"cols": BASE_COLS + extra, "rows": BASE_ROWS + extra}


def next_expansion(db: Session) -> dict[str, Any] | None:
    """Следующее доступное расширение (ещё не купленное)."""
    bought = expansions_bought(db)
    keys = sorted(EXPANSIONS)
    if bought >= len(keys):
        return None
    key = keys[bought]
    return {"key": key, **EXPANSIONS[key]}


def species_catalog(level: int) -> list[dict[str, Any]]:
    """Каталог видов с пометкой «открыт на этом уровне»."""
    return [{**s, "unlocked": level >= s["level"]} for s in SPECIES]


def species_for(level: int, task_id: int, rarity: str) -> str:
    """Вид нового растения: открытые уровнем виды, premium — только rare/epic.

    Детерминированно от task_id — повторный вызов даёт тот же вид.
    """
    pool = [
        s["key"]
        for s in SPECIES
        if level >= s["level"] and (not s["premium"] or rarity in ("rare", "epic"))
    ]
    return str(pool[task_id % len(pool)])


def house_footprint(cols: int, rows: int) -> set[tuple[int, int]]:
    cx = cols // 2 - HOUSE_COLS // 2
    cy = rows // 2 - HOUSE_ROWS // 2
    return {
        (x, y) for x in range(cx, cx + HOUSE_COLS) for y in range(cy, cy + HOUSE_ROWS)
    }


def spiral_position(
    occupied: set[tuple[int, int]], cols: int, rows: int
) -> tuple[int, int]:
    """Следующая свободная ячейка по спирали вокруг домика в центре."""
    cx, cy = cols // 2, rows // 2
    radius = SPIRAL_START_RADIUS
    while radius < max(cols, rows):
        for dx in range(-radius, radius + 1):
            for dy in range(-radius, radius + 1):
                if max(abs(dx), abs(dy)) != radius:
                    continue
                x, y = cx + dx, cy + dy
                if 0 <= x < cols and 0 <= y < rows and (x, y) not in occupied:
                    return (x, y)
        radius += 1
    # сетка кончилась — первая свободная ячейка по порядку
    for y in range(rows):
        for x in range(cols):
            if (x, y) not in occupied:
                return (x, y)
    return (0, 0)


def _level_at_event(db: Session, event: XpEvent) -> int:
    """Уровень садовника на момент события XP — по сумме XP событий до него."""
    xp_up_to = db.scalar(
        select(func.coalesce(func.sum(XpEvent.amount), 0)).where(XpEvent.id <= event.id)
    )
    return level_from_xp(int(xp_up_to or 0))


def ensure_garden(db: Session) -> None:
    """Ленивое обслуживание сада: бонусы уровней + авторазмещение новых растений."""
    # 1) Бонусы за достигнутые уровни (задним числом — для старых уровней)
    reached = level_from_xp(total_xp(db))
    granted = set(
        db.scalars(
            select(CoinEvent.level).where(
                CoinEvent.source == "level", CoinEvent.level.is_not(None)
            )
        ).all()
    )
    for level in range(2, reached + 1):
        if level not in granted:
            db.add(CoinEvent(source="level", level=level, amount=level_bonus_coins(level)))

    # 2) Растения для событий XP без размещённого элемента
    placed_refs = set(
        db.scalars(
            select(GardenItem.ref_id).where(
                GardenItem.kind == "plant", GardenItem.ref_id.is_not(None)
            )
        ).all()
    )
    pending = [
        e
        for e in db.scalars(select(XpEvent).order_by(XpEvent.id)).all()
        if e.id not in placed_refs
    ]
    if pending:
        size = grid_size(db)
        # Неразмещенные декорации (в инвентаре) ячейки не занимают
        occupied = {
            (int(x), int(y))
            for x, y in db.execute(
                select(GardenItem.x, GardenItem.y).where(
                    GardenItem.x.is_not(None), GardenItem.y.is_not(None)
                )
            ).all()
        } | house_footprint(size["cols"], size["rows"])
        for event in pending:
            # вид и уровень — на момент события, чтобы старые закрытия
            # получили вид из пула своего времени
            species = species_for(
                _level_at_event(db, event), event.task_id or 0, event.rarity
            )
            x, y = spiral_position(occupied, size["cols"], size["rows"])
            occupied.add((x, y))
            db.add(GardenItem(kind="plant", ref_id=event.id, item_key=species, x=x, y=y))
    db.flush()


def can_afford(db: Session, cost: int) -> bool:
    return balance(db) >= cost


def spend(db: Session, cost: int, item_key: str) -> None:
    db.add(CoinEvent(source="shop", amount=-cost, item_key=item_key))


