"""Dashboard aggregation — assembles the /dashboard landing payload.

One call: most-recently-active projects (each -> active milestones -> open
tasks), recently-completed tasks, week stats. Owner-scoped,
trashed rows excluded. Each section is independent — a failure returns its
empty value rather than blanking the page.
"""
from __future__ import annotations
import logging
from datetime import datetime, timedelta, timezone

from sqlalchemy import case, func, select

from scribe.models import async_session
from scribe.models.note import Note
from scribe.models.project import Project
from scribe.models.milestone import Milestone
from scribe.models.base import iso
from scribe.services import milestones as milestones_svc

logger = logging.getLogger(__name__)

N_PROJECTS = 3        # most-recently-active projects shown
TASKS_PER_GROUP = 5   # open-task cap per milestone / no-milestone group
RECENT_DONE_LIMIT = 8 # recently-completed tasks shown
OPEN_ISSUES_LIMIT = 10 # open issues shown on the dashboard
WINDOW_DAYS = 7       # look-back window (done items, week stats)
_OPEN = ["todo", "in_progress"]


def _open_order():
    """in-progress first -> priority high..none -> most-recently-updated."""
    status_rank = case((Note.status == "in_progress", 0), else_=1)
    priority_rank = case(
        (Note.priority == "high", 0), (Note.priority == "medium", 1),
        (Note.priority == "low", 2), else_=3,
    )
    return status_rank, priority_rank, Note.updated_at.desc()


def _task_row(n: Note) -> dict:
    return {"id": n.id, "title": n.title, "status": n.status,
            "priority": n.priority or "none"}


async def _safe(coro, empty):
    try:
        return await coro
    except Exception:
        logger.warning("dashboard section failed", exc_info=True)
        return empty


async def _open_issues(user_id: int) -> list[dict]:
    """Open issues (task_kind='issue', not done/cancelled) across the owner's
    projects, ranked like the other task lists. Owner-scoped, matching the rest
    of the dashboard."""
    async with async_session() as session:
        rows = await session.execute(
            select(
                Note.id, Note.title, Note.status, Note.priority,
                Note.project_id, Project.title,
            )
            .join(Project, Project.id == Note.project_id, isouter=True)
            .where(
                Note.user_id == user_id,
                Note.task_kind == "issue",
                Note.status.in_(_OPEN),
                Note.deleted_at.is_(None),
            )
            .order_by(*_open_order())
            .limit(OPEN_ISSUES_LIMIT)
        )
        return [
            {
                "id": iid, "title": title, "status": status,
                "priority": priority or "none",
                "project_id": pid, "project_title": pname,
            }
            for iid, title, status, priority, pid, pname in rows.fetchall()
        ]


async def build_dashboard(user_id: int) -> dict:
    return {
        "active_projects": await _safe(_active_projects(user_id), []),
        "recently_completed": await _safe(_recently_completed(user_id), []),
        "open_issues": await _safe(_open_issues(user_id), []),
        "week_stats": await _safe(_week_stats(user_id), {}),
    }


async def _active_projects(user_id: int) -> list[dict]:
    so, po, ro = _open_order()
    async with async_session() as session:
        recency = (
            select(Note.project_id, func.max(Note.updated_at).label("last"))
            .where(Note.user_id == user_id, Note.deleted_at.is_(None),
                   Note.project_id.isnot(None))
            .group_by(Note.project_id).subquery()
        )
        prows = (await session.execute(
            select(Project, recency.c.last)
            .outerjoin(recency, Project.id == recency.c.project_id)
            .where(Project.user_id == user_id, Project.status == "active",
                   Project.deleted_at.is_(None))
            .order_by(func.coalesce(recency.c.last, Project.updated_at).desc())
            .limit(N_PROJECTS)
        )).all()

        out = []
        for project, last in prows:
            counts = (await session.execute(
                select(
                    func.count(Note.id).filter(Note.status.in_(_OPEN)),
                    func.count(Note.id).filter(Note.status == "done"),
                    func.count(Note.id).filter(Note.status.in_(_OPEN + ["done"])),
                ).where(Note.user_id == user_id, Note.project_id == project.id,
                        Note.deleted_at.is_(None))
            )).one()
            open_count, done_count, resolved_total = counts
            progress_pct = round(done_count / resolved_total * 100, 1) if resolved_total else 0.0

            mrows = (await session.execute(
                select(Milestone).where(
                    Milestone.user_id == user_id, Milestone.project_id == project.id,
                    Milestone.status == "active", Milestone.deleted_at.is_(None))
                .order_by(Milestone.order_index.asc())
            )).scalars().all()
            milestones = []
            for m in mrows:
                tasks = (await session.execute(
                    select(Note).where(
                        Note.user_id == user_id, Note.milestone_id == m.id,
                        Note.status.in_(_OPEN), Note.deleted_at.is_(None))
                    .order_by(so, po, ro).limit(TASKS_PER_GROUP)
                )).scalars().all()
                if not tasks:
                    continue
                prog = await milestones_svc.get_milestone_progress(m.id)
                milestones.append({
                    "id": m.id, "title": m.title, "progress_pct": prog["pct"],
                    "open_tasks": [_task_row(t) for t in tasks],
                })

            no_ms = (await session.execute(
                select(Note).where(
                    Note.user_id == user_id, Note.project_id == project.id,
                    Note.milestone_id.is_(None), Note.status.in_(_OPEN),
                    Note.deleted_at.is_(None))
                .order_by(so, po, ro).limit(TASKS_PER_GROUP)
            )).scalars().all()

            out.append({
                "id": project.id, "title": project.title, "color": project.color,
                "last_activity": (last or project.updated_at).isoformat(),
                "open_count": open_count, "done_count": done_count,
                "progress_pct": progress_pct,
                "milestones": milestones,
                "no_milestone": [_task_row(t) for t in no_ms],
            })
        return out


async def _recently_completed(user_id: int) -> list[dict]:
    cutoff = datetime.now(timezone.utc) - timedelta(days=WINDOW_DAYS)
    async with async_session() as session:
        rows = (await session.execute(
            select(Note, Project.title)
            .outerjoin(Project, Note.project_id == Project.id)
            .where(Note.user_id == user_id, Note.status == "done",
                   Note.deleted_at.is_(None), Note.completed_at.isnot(None),
                   Note.completed_at >= cutoff)
            .order_by(Note.completed_at.desc()).limit(RECENT_DONE_LIMIT)
        )).all()
        return [{"id": n.id, "title": n.title, "project_title": ptitle,
                 "completed_at": iso(n.completed_at)} for n, ptitle in rows]


async def _week_stats(user_id: int) -> dict:
    cutoff = datetime.now(timezone.utc) - timedelta(days=WINDOW_DAYS)
    async with async_session() as session:
        row = (await session.execute(
            select(
                func.count(Note.id).filter(Note.status == "done", Note.completed_at >= cutoff),
                func.count(Note.id).filter(Note.status.in_(_OPEN)),
                func.count(Note.id).filter(Note.status == "in_progress"),
                func.count(Note.id).filter(Note.task_kind == "plan", Note.status.in_(_OPEN)),
            ).where(Note.user_id == user_id, Note.deleted_at.is_(None))
        )).one()
        return {"completed_this_week": row[0], "open_total": row[1],
                "in_progress": row[2], "active_plans": row[3]}
