"""workspace/coverage.py — coverage reduction + best-status rollup.

Extracted from workspace/server.py in MF-5 (Engine Fix Sprint Phase B,
2026-04-30). Console v2's v2_dispatch.py calls these functions directly
for per-episode coverage badges and per-take best-status rollups, without
going through legacy web-framework routes.

Public surface declared in __all__.
"""
from __future__ import annotations

import logging
import sys
import time as _time
from pathlib import Path

from recoil.execution.pass_store import PassStore
from recoil.workspace.helpers import get_store as _get_store

# ── Tenet 6: typed-exception import ───────────────────────────
_RECOIL_ROOT = Path(__file__).resolve().parent.parent
if str(_RECOIL_ROOT) not in sys.path:
    sys.path.insert(0, str(_RECOIL_ROOT))
from recoil.core.exceptions import ExecutionStoreUnavailableError  # noqa: E402

log = logging.getLogger("recoil.workspace.coverage")

__all__ = [
    "STATUS_PRIORITY",
    "best_status",
    "coverage_summary_for_episode",
    "recent_activity_for_episode",
]


# Status priority for picking the "best" status across takes in a shot group.
# Higher number = better status.
STATUS_PRIORITY: dict[str, int] = {
    "approved": 100,
    "video_complete": 90,
    "needs_review": 70,
    "icu_escalated": 60,
    "pending_qc": 60,
    "generating": 50,
    "submitted": 50,
    "processing": 50,
    "downloading": 50,
    "previs_complete": 40,
    "previs_pending": 20,
    "failed": 10,
    "rejected": 10,
    "untracked": 0,
}


def best_status(statuses: list[str]) -> str:
    """Return the best status from a list, using priority ranking."""
    if not statuses:
        return "untracked"
    return max(statuses, key=lambda s: STATUS_PRIORITY.get(s, 0))


def coverage_summary_for_episode(project: str, episode_id: str) -> dict:
    """Compute coverage summary for the episode header."""
    try:
        store_exec = _get_store(project)
        shots = [
            s
            for s in store_exec.get_all_shots()
            if s.get("shot_id", "").startswith(episode_id + "_")
        ]
        total = len(shots)
        store_exec.close()
    except Exception as e:
        # Tenet 6: store failure must not silently render 0/0 as 100%
        # coverage. Callers (HTTP routes) wrap this in HTTP 503.
        log.exception("coverage_summary: store unavailable for %s", project)
        raise ExecutionStoreUnavailableError(
            f"could not read coverage for project={project}: {e}"
        ) from e

    store = PassStore(project)
    try:
        passes = store.list_passes(episode_id)
    finally:
        store.close()

    covered_set: set[str] = set()
    review = 0
    for p in passes:
        if p.get("status") == "segmentation_review":
            review += 1
        for sid in p.get("segment_shot_ids") or []:
            covered_set.add(sid)

    covered = len(covered_set)
    awaiting = max(0, total - covered)
    label = f"{covered}/{total} covered · {awaiting} awaiting · {review} review"
    return {
        "type": "coverage_summary",
        "name": label,
        "covered": covered,
        "total": total,
        "awaiting": awaiting,
        "review": review,
    }


def recent_activity_for_episode(project: str, episode_id: str) -> dict | None:
    """Return a recent_activity node listing passes created in the last 24h."""
    cutoff = _time.time() - 86400.0
    store = PassStore(project)
    try:
        passes = store.list_passes(episode_id)
    finally:
        store.close()

    entries = []
    for p in passes:
        ca = p.get("created_at") or 0
        if ca >= cutoff:
            entries.append(
                {
                    "type": "recent_pass",
                    "name": p.get("pass_id", ""),
                    "pass_id": p.get("pass_id", ""),
                    "take_count": len(p.get("takes") or []),
                    "created_at": ca,
                }
            )
    if not entries:
        return None
    entries.sort(key=lambda e: e["created_at"], reverse=True)
    return {
        "type": "recent_activity",
        "name": f"Recent ({len(entries)} passes, last 24h)",
        "children": entries,
    }
