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

from typing import Any

from fastapi.testclient import TestClient

from app.services.xp import compute_rarity


def _create_task(client: TestClient, **patch: Any) -> int:
    resp = client.post("/api/tasks", json={"title": "Задача для XP"})
    task_id = resp.json()["id"]
    if patch:
        resp2 = client.patch(f"/api/tasks/{task_id}", json=patch)
        assert resp2.status_code == 200
    return task_id


def test_xp_granted_on_done(client: TestClient) -> None:
    task_id = _create_task(client)
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    assert resp.status_code == 200
    # база 10, без приоритета и оценки — только база
    assert resp.headers.get("X-Earned-XP") == "10"

    summary = client.get("/api/xp").json()
    assert summary["total_xp"] == 10
    assert summary["level"] == 1
    assert summary["completed_count"] == 1


def test_xp_weighted_by_priority_and_estimate(client: TestClient) -> None:
    task_id = _create_task(client)
    client.patch(
        f"/api/tasks/{task_id}", json={"priority": 9, "estimated_minutes": 480}
    )
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    # 10 + urgent 15 + >4ч 15 = 40
    assert resp.headers.get("X-Earned-XP") == "40"


def test_xp_granted_once(client: TestClient) -> None:
    task_id = _create_task(client)
    client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    # возврат в работу и повторное закрытие — XP не растёт
    client.patch(f"/api/tasks/{task_id}", json={"status": "in_progress"})
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    assert "X-Earned-XP" not in resp.headers
    assert client.get("/api/xp").json()["total_xp"] == 10


def test_deleting_task_keeps_xp(client: TestClient) -> None:
    task_id = _create_task(client)
    client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    client.delete(f"/api/tasks/{task_id}")
    assert client.get("/api/xp").json()["total_xp"] == 10


def test_level_progression(client: TestClient) -> None:
    # 9 закрытий по 10 XP = 90 — уровень 1; 10 закрытий = 100 — уровень 2
    for _ in range(9):
        task_id = _create_task(client)
        client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    assert client.get("/api/xp").json()["level"] == 1
    task_id = _create_task(client)
    client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    summary = client.get("/api/xp").json()
    assert summary["level"] == 2
    assert summary["level_xp"] == 0
    assert summary["level_span"] == 200
    assert summary["level_percent"] == 0


def test_compute_rarity_boundaries() -> None:
    # epic: только крупные закрытия и только при малом roll
    assert compute_rarity(30, 0.079) == "epic"
    assert compute_rarity(30, 0.081) == "rare"  # мимо epic, но rare-шанс 45%
    assert compute_rarity(30, 0.5) == "common"
    assert compute_rarity(20, 0.5) == "common"  # при xp < 25 epic недоступен вообще
    # rare: шанс 5% + (xp-10)*2%; xp=10 -> 5%, xp=40 -> 65%
    assert compute_rarity(10, 0.049) == "rare"
    assert compute_rarity(10, 0.05) == "common"
    assert compute_rarity(40, 0.649) == "rare"
    assert compute_rarity(40, 0.65) == "common"
    # при большом roll всегда common
    assert compute_rarity(40, 0.99) == "common"


def test_xp_granted_with_rarity_and_options(client: TestClient) -> None:
    task_id = _create_task(client)
    resp = client.patch(
        f"/api/tasks/{task_id}", json={"status": "done", "earned_via": "options"}
    )
    assert resp.status_code == 200
    assert resp.headers.get("X-Earned-XP") == "10"
    assert resp.headers.get("X-Plant-Rarity") == "common"
    assert resp.headers.get("X-Options-Close") == "1"

    events = client.get("/api/xp/events").json()
    assert len(events) == 1
    assert events[0]["task_id"] == task_id
    assert events[0]["amount"] == 10
    assert events[0]["rarity"] == "common"
    assert events[0]["via_options"] is True

    # закрытие из обычного списка — via_options не ставится
    task2 = _create_task(client)
    resp2 = client.patch(f"/api/tasks/{task2}", json={"status": "done"})
    assert resp2.headers.get("X-Plant-Rarity") == "common"
    assert "X-Options-Close" not in resp2.headers
    events = client.get("/api/xp/events").json()
    assert {e["via_options"] for e in events} == {True, False}


def test_rarity_persists_after_grant(client: TestClient) -> None:
    # событие с rarity (common при большинстве роллов) переживает перечитывание
    task_id = _create_task(client)
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    rarity = resp.headers.get("X-Plant-Rarity")
    assert rarity in ("common", "rare", "epic")
    events = client.get("/api/xp/events").json()
    assert events[0]["rarity"] == rarity


def test_mentally_hard_bonus(client: TestClient) -> None:
    # метка «ментально сложная» (ТЗ 3.13): +10 к XP при закрытии
    task_id = _create_task(client, mentally_hard=True)
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    # 10 база + 10 за силу воли
    assert resp.headers.get("X-Earned-XP") == "20"
    assert resp.headers.get("X-Mentally-Hard") == "1"

    # флаг виден в выдаче задачи
    assert client.get(f"/api/tasks/{task_id}").json()["mentally_hard"] is True


def test_mentally_hard_toggle_before_done(client: TestClient) -> None:
    # переключатель: метку можно снять до закрытия — бонус пропадает
    task_id = _create_task(client, mentally_hard=True)
    resp = client.patch(f"/api/tasks/{task_id}", json={"mentally_hard": False})
    assert resp.json()["mentally_hard"] is False
    resp = client.patch(f"/api/tasks/{task_id}", json={"status": "done"})
    assert resp.headers.get("X-Earned-XP") == "10"
    assert "X-Mentally-Hard" not in resp.headers
