"""Геймификация: XP за закрытые задачи и уровни (ТЗ 3.13).

Только позитивное подкрепление: XP только начисляется, ничего не сгорает и
не отнимается (удаление задачи не трогает заработанное, стриков/серий нет).
"""

import random
from typing import Any

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

from app.models import Task, XpEvent

# База за любую закрытую задачу
XP_BASE = 10
# Бонус за приоритет (по градации шкалы 0-10: very_low/low/medium/high/urgent)
PRIORITY_BONUS = {"very_low": 0, "low": 2, "medium": 5, "high": 10, "urgent": 15}
# Бонус за оценку времени: больше работа — больше награда (минуты -> бонус,
# больше 240 -> последний бонус)
ESTIMATE_BONUS = ((15, 2), (60, 5), (240, 10), (float("inf"), 15))

# Уровень L достигается при XP >= LEVEL_FACTOR * (L-1) * L / 2:
# L2 = 100, L3 = 300, L4 = 600, L5 = 1000 ... на уровень L -> L+1 нужно 100*L
LEVEL_FACTOR = 100


def priority_grade(priority: int | None) -> str:
    if priority is None:
        return "very_low"
    if priority <= 2:
        return "very_low"
    if priority <= 4:
        return "low"
    if priority <= 6:
        return "medium"
    if priority <= 8:
        return "high"
    return "urgent"


def compute_task_xp(task: Task) -> int:
    """XP за задачу: база + бонус приоритета + бонус объёма (оценки времени)."""
    xp = XP_BASE + PRIORITY_BONUS[priority_grade(task.priority)]
    minutes = task.estimated_minutes
    if minutes is not None:
        for limit, bonus in ESTIMATE_BONUS:
            if minutes <= limit:
                xp += bonus
                break
    return xp


# Редкость растения (ТЗ 3.13): чем «весомее» задача (больше XP), тем выше шанс.
# epic — только для крупных закрытий; rare — линейный рост от XP.
EPIC_XP_THRESHOLD = 25
EPIC_CHANCE = 0.08
RARE_BASE_CHANCE = 0.05
RARE_CHANCE_PER_XP = 0.02


def compute_rarity(xp: int, roll: float) -> str:
    """Редкость растения по весу задачи: roll из random.random() (для тестов подаётся).

    Порядок проверки: epic -> rare -> common.
    """
    if xp >= EPIC_XP_THRESHOLD and roll < EPIC_CHANCE:
        return "epic"
    rare_chance = RARE_BASE_CHANCE + max(xp - XP_BASE, 0) * RARE_CHANCE_PER_XP
    if roll < rare_chance:
        return "rare"
    return "common"


def grant_task_xp(db: Session, task: Task) -> XpEvent | None:
    """Начислить XP за закрытие задачи (один раз на задачу, идемпотентно).

    Возвращает созданное событие (с rarity) или None, если XP уже начислен.
    """
    existing = db.scalar(select(XpEvent).where(XpEvent.task_id == task.id))
    if existing is not None:
        return None
    amount = compute_task_xp(task)
    event = XpEvent(
        task_id=task.id,
        amount=amount,
        rarity=compute_rarity(amount, random.random()),
    )
    db.add(event)
    db.flush()
    return event


def total_xp(db: Session) -> int:
    return int(db.scalar(select(func.coalesce(func.sum(XpEvent.amount), 0))) or 0)


def level_from_xp(xp: int) -> int:
    """Наибольший уровень L, при котором xp >= LEVEL_FACTOR*(L-1)*L/2."""
    level = 1
    while xp >= LEVEL_FACTOR * level * (level + 1) // 2:
        level += 1
    return level


def xp_summary(db: Session) -> dict[str, Any]:
    """Сводка для UI: XP, уровень, прогресс до следующего уровня."""
    xp = total_xp(db)
    level = level_from_xp(xp)
    current_floor = LEVEL_FACTOR * (level - 1) * level // 2
    next_floor = LEVEL_FACTOR * level * (level + 1) // 2
    span = next_floor - current_floor
    return {
        "total_xp": xp,
        "level": level,
        "level_xp": xp - current_floor,
        "level_span": span,
        "level_percent": round((xp - current_floor) / span * 100) if span else 100,
        "completed_count": int(db.scalar(select(func.count(XpEvent.id))) or 0),
    }


def xp_events(db: Session) -> list[dict[str, Any]]:
    """Все события начисления (для сада: редкости, счётчик «выбрал и сделал», история)."""
    rows = db.scalars(select(XpEvent).order_by(XpEvent.created_at.desc())).all()
    return [
        {
            "task_id": r.task_id,
            "amount": r.amount,
            "rarity": r.rarity,
            "via_options": r.via_options,
            "created_at": r.created_at,
        }
        for r in rows
    ]
