"""Прогнозирование времени и режим «3 варианта» (M4).

Прогноз — медиана фактической длительности похожих завершённых задач
(похожесть: общий проект или хотя бы один тег). Без истории — None.
LLM-оценка приходит в предложении детализации и применяется при утверждении.
"""

import statistics
from typing import Any

from sqlalchemy import select

from app.models import Project, Task


def predict_minutes(db: Any, task: Task) -> int | None:
    """Прогноз длительности по истории похожих завершённых задач."""
    candidates: list[int] = []
    task_tag_ids = [t.id for t in task.tags]
    done = list(
        db.scalars(
            select(Task).where(Task.status == "done", Task.actual_minutes.isnot(None))
        ).all()
    )
    for other in done:
        if other.id == task.id:
            continue
        similar = other.project_id is not None and other.project_id == task.project_id
        if not similar and task_tag_ids and other.tags:
            similar = bool(set(t.id for t in other.tags) & set(task_tag_ids))
        if similar:
            candidates.append(other.actual_minutes or 0)
    candidates = [c for c in candidates if c > 0]
    if not candidates:
        return None
    return int(round(statistics.median(candidates)))


def pick_options(db: Any, available_minutes: int, limit: int = 3) -> list[Task]:
    """«3 варианта» (ТЗ 3.8): задачи, подходящие под доступное время.

    Кандидаты: утверждённые, к выполнению/в работе, проект активен (или без
    проекта). Сортировка: приоритет, затем свежесть. Разнообразие: максимум
    одна задача на проект; сначала строго укладывающиеся в время.
    """
    stmt = (
        select(Task)
        .where(Task.detail_state == "approved", Task.status.in_(("to_do", "in_progress")))
        .order_by(Task.priority.desc().nullslast(), Task.created_at.asc())
    )
    tasks = list(db.scalars(stmt).all())

    active_projects: set[int | None] = {None}
    for p in db.scalars(select(Project)).all():
        if p.relevance_status == "active":
            active_projects.add(p.id)

    used_projects: set[int | None] = set()
    picked_ids: set[int] = set()
    picked: list[Task] = []
    # сначала — строго укладывающиеся в доступное время
    for t in tasks:
        if len(picked) >= limit:
            break
        if t.project_id not in active_projects or t.project_id in used_projects:
            continue
        if t.estimated_minutes is not None and t.estimated_minutes > available_minutes:
            continue
        picked.append(t)
        picked_ids.add(t.id)
        used_projects.add(t.project_id)
    # добираем задачи без оценки длительности (её нельзя отсечь по времени)
    if len(picked) < limit:
        for t in tasks:
            if len(picked) >= limit:
                break
            if t.estimated_minutes is not None:
                continue  # с оценкой: либо уложились в первый проход, либо не влезли
            if t.id in picked_ids or t.project_id not in active_projects:
                continue
            if t.project_id in used_projects:
                continue
            picked.append(t)
            picked_ids.add(t.id)
            used_projects.add(t.project_id)
    return picked[:limit]
