"""Регулярные задачи (ТЗ 3.5).

Правила (решение пользователя 2026-09-19): фиксированный календарь —
следующий экземпляр отсчитывается от даты выполнения по календарю:
- interval: каждые N дней от якорной даты (кратно интервалу);
- weekdays: ближайший следующий день из выбранных (ISO: пн=1..вс=7);
- monthly: указанное число месяца (если дня нет — последний день месяца).

Новый экземпляр создаётся при завершении предыдущего: копируются метаданные,
status=to_do, detail_state=approved, дедлайн-дата не переносится (новому
экземпляру её задаёт пользователь), нестрогий период сохраняется.
"""

import calendar
from datetime import UTC, date, datetime, timedelta

from sqlalchemy.orm import Session

from app.models import Task

WEEKDAY_NAMES = {1: "пн", 2: "вт", 3: "ср", 4: "чт", 5: "пт", 6: "сб", 7: "вс"}


def next_date(task: Task, after: date) -> date | None:
    """Дата следующего экземпляра по правилу, строго после `after` (календарь)."""
    if task.recur_kind == "interval":
        step = task.recur_interval_days or 1
        anchor = task.created_at.date()
        # ближайшая дата якоря + k*step, строго больше `after`
        k = max(0, (after - anchor).days // step) + 1
        return date.fromordinal(anchor.toordinal() + k * step)
    if task.recur_kind == "weekdays":
        if not task.recur_weekdays:
            return None
        days = {int(d) for d in task.recur_weekdays.split(",")}
        candidate = after
        for _ in range(7):
            candidate += timedelta(days=1)
            if candidate.isoweekday() in days:
                return candidate
        return None
    if task.recur_kind == "monthly":
        if not task.recur_day_of_month:
            return None
        day = task.recur_day_of_month
        year, month = after.year, after.month
        while True:
            last_day = calendar.monthrange(year, month)[1]
            candidate = date(year, month, min(day, last_day))
            if candidate > after:
                return candidate
            year, month = (year + 1, 1) if month == 12 else (year, month + 1)
    return None


def recurrence_label(task: Task) -> str:
    """Короткое описание правила (для бейджей UI)."""
    if task.recur_kind == "interval":
        step = task.recur_interval_days or 1
        if step == 1:
            return "ежедневно"
        if step == 7:
            return "еженедельно"
        return f"каждые {step} дн."
    if task.recur_kind == "weekdays":
        if task.recur_weekdays:
            return " ".join(WEEKDAY_NAMES[int(d)] for d in task.recur_weekdays.split(","))
        return "по дням недели"
    if task.recur_kind == "monthly":
        return f"ежемесячно {task.recur_day_of_month}-го"
    return "регулярная"


def _copy(db: Session, source: Task) -> Task:
    """Новый экземпляр регулярной задачи: те же метаданные, чистое состояние."""
    clone = Task(
        title=source.title,
        description=source.description,
        task_type="recurring",
        status="to_do",
        detail_state="approved",  # метаданные уже утверждены пользователем
        project_id=source.project_id,
        priority=source.priority,
        estimated_minutes=source.estimated_minutes,
        budget_money=source.budget_money,
        cost_estimate_money=source.cost_estimate_money,
        deadline_period=source.deadline_period,
        recur_kind=source.recur_kind,
        recur_interval_days=source.recur_interval_days,
        recur_weekdays=source.recur_weekdays,
        recur_day_of_month=source.recur_day_of_month,
    )
    clone.tags = list(source.tags)
    db.add(clone)
    return clone


def spawn_next(db: Session, task: Task) -> Task | None:
    """Создать следующий экземпляр после завершения регулярной задачи.

    Фиксированный календарь: следующий день строго после даты выполнения.
    Если следующий день по правилу найти нельзя — None.
    Коммит делает вызывающий код.
    """
    if task.task_type != "recurring" or task.recur_kind is None:
        return None
    done_day = (task.done_at or datetime.now(UTC)).date()
    if next_date(task, done_day) is None:
        return None
    return _copy(db, task)
