"""Тесты: дедлайны (ТЗ 3.4) и регулярные задачи (ТЗ 3.5)."""

from datetime import date, timedelta
from typing import Any

from fastapi.testclient import TestClient

from app.models import Task
from app.services.options import pick_options
from app.services.recurrence import next_date, recurrence_label, spawn_next
from tests.conftest import _test_session_factory


def test_session() -> Any:
    return _test_session_factory()


# --- дедлайны ---


def test_deadlines_set_and_cleared(client: TestClient) -> None:
    tid = client.post("/api/tasks", json={"title": "С дедлайном"}).json()["id"]
    updated = client.patch(
        f"/api/tasks/{tid}",
        json={"deadline_date": "2026-10-01", "deadline_period": "week"},
    ).json()
    assert updated["deadline_date"] == "2026-10-01"
    assert updated["deadline_period"] == "week"

    # неизвестный период — 422
    assert (
        client.patch(f"/api/tasks/{tid}", json={"deadline_period": "fortnight"}).status_code == 422
    )

    # очистка
    cleared = client.patch(f"/api/tasks/{tid}", json={"deadline_date": None}).json()
    assert cleared["deadline_date"] is None


def test_options_sorted_by_deadline_urgency(client: TestClient) -> None:
    # по проекту на задачу: правило «≤1 на проект» не смешивает результаты
    def approve(title: str, **patch: Any) -> None:
        pid = client.post("/api/projects", json={"name": title}).json()["id"]
        tid = client.post("/api/tasks", json={"title": title}).json()["id"]
        client.patch(f"/api/tasks/{tid}", json={"project_id": pid, **patch})
        client.post(f"/api/tasks/{tid}/approve")

    tomorrow = (date.today() + timedelta(days=2)).isoformat()
    far = (date.today() + timedelta(days=60)).isoformat()
    approve("Близкий дедлайн", deadline_date=tomorrow)
    approve("Далёкий дедлайн", deadline_date=far)
    approve("Мягкий дедлайн", deadline_period="week")
    approve("Без дедлайна")

    options = pick_options(test_session(), 600)
    titles = [t.title for t in options]
    # срочность: близкий строгий → далёкий строгий → нестрогий период;
    # лимит 3 — задача без дедлайна остаётся за бортом
    assert "Без дедлайна" not in titles
    assert titles.index("Близкий дедлайн") < titles.index("Далёкий дедлайн")
    assert titles.index("Далёкий дедлайн") < titles.index("Мягкий дедлайн")


# --- правила повторения ---


def test_next_date_interval() -> None:
    task = Task(
        title="t",
        recur_kind="interval",
        recur_interval_days=7,
        created_at=__import__("datetime").datetime(2026, 9, 1, 12, 0),
    )
    assert next_date(task, date(2026, 9, 3)) == date(2026, 9, 8)
    assert next_date(task, date(2026, 9, 8)) == date(2026, 9, 15)  # строго после


def test_next_date_weekdays() -> None:
    task = Task(title="t", recur_kind="weekdays", recur_weekdays="1,3,5")  # пн, ср, пт
    # 2026-09-19 — суббота; ближайший следующий из пн/ср/пт — понедельник 21-го
    assert next_date(task, date(2026, 9, 19)) == date(2026, 9, 21)


def test_next_date_monthly_clamps_to_month_end() -> None:
    task = Task(title="t", recur_kind="monthly", recur_day_of_month=31)
    assert next_date(task, date(2026, 2, 15)) == date(2026, 2, 28)  # 2026 не високосный
    assert next_date(task, date(2026, 2, 28)) == date(2026, 3, 31)


def test_recurrence_label() -> None:
    assert recurrence_label(Task(title="t", recur_kind="interval", recur_interval_days=1)) == (
        "ежедневно"
    )
    assert recurrence_label(Task(title="t", recur_kind="weekdays", recur_weekdays="1,3")) == (
        "пн ср"
    )
    assert recurrence_label(Task(title="t", recur_kind="monthly", recur_day_of_month=1)) == (
        "ежемесячно 1-го"
    )


# --- спавн следующего экземпляра ---


def test_recurring_task_spawns_next_on_done(client: TestClient) -> None:
    tid = client.post("/api/tasks", json={"title": "Полить цветы"}).json()["id"]
    client.patch(
        f"/api/tasks/{tid}",
        json={
            "task_type": "recurring",
            "recur_kind": "interval",
            "recur_interval_days": 3,
            "estimated_minutes": 10,
        },
    )
    client.post(f"/api/tasks/{tid}/approve")
    client.patch(f"/api/tasks/{tid}", json={"status": "done"})

    tasks = client.get("/api/tasks?status=to_do").json()
    spawned = [t for t in tasks if t["title"] == "Полить цветы"]
    assert len(spawned) == 1
    new = spawned[0]
    assert new["task_type"] == "recurring"
    assert new["detail_state"] == "approved"  # метаданные уже утверждены
    assert new["estimated_minutes"] == 10
    assert new["recur_kind"] == "interval"


def test_one_time_task_does_not_spawn(client: TestClient) -> None:
    tid = client.post("/api/tasks", json={"title": "Разовая"}).json()["id"]
    client.post(f"/api/tasks/{tid}/approve")
    client.patch(f"/api/tasks/{tid}", json={"status": "done"})
    assert client.get("/api/tasks?status=to_do").json() == []


def test_spawn_next_without_rule_is_none() -> None:
    task = Task(title="t", task_type="recurring")
    session = test_session()
    session.add(task)
    session.flush()
    assert spawn_next(session, task) is None
