"""ADR status rendering functions for the status subsystem."""

from typing import Any, cast

from rich import box
from rich.table import Table

from gzkit.commands.common import (
    _is_pool_adr_id,
    console,
)
from gzkit.commands.status_obpi import (
    _print_status_obpi_section,
)

# ---------------------------------------------------------------------------
# Table category labels (importable for test assertions)
# ---------------------------------------------------------------------------

TABLE_TITLE_FOUNDATION = "Foundation ADRs"
TABLE_TITLE_FEATURE = "Feature ADRs"
TABLE_TITLE_POOL = "Pool ADRs"

# ---------------------------------------------------------------------------
# Gate / QC rendering
# ---------------------------------------------------------------------------


def _render_gate_status(gate_status: str | None) -> str:
    if gate_status == "pass":
        return "[green]PASS[/green]"
    if gate_status == "fail":
        return "[red]FAIL[/red]"
    if gate_status == "n/a":
        return "[cyan]N/A[/cyan]"
    return "[yellow]PENDING[/yellow]"


def _qc_readiness(
    gates: dict[str, str],
    lane: str,
    obpi_summary: dict[str, Any],
    observed_post_validation_failures: list[int] | None = None,
) -> tuple[str, list[str]]:
    """Derive summarized QC readiness and pending checkpoints from gate statuses.

    `observed_post_validation_failures` carries gate ids whose latest raw
    observation is `gate_checked: fail` after the validated lifecycle epoch
    began (per `Ledger.get_post_validation_failed_gates`). The lifecycle-
    authoritative `gates` dict shows those gates as `pass`; the sidecar adds
    a blocker so QC readiness does not silently report `READY` while live
    evidence contradicts the smoothed view (GHI #411 — observation
    laundering).
    """
    blockers: list[str] = []
    obpi_total = int(obpi_summary.get("total", 0))
    obpi_unit_status = str(obpi_summary.get("unit_status", "unscoped"))
    if obpi_total > 0 and obpi_unit_status != "completed":
        blockers.append("OBPI completion")

    if gates.get("2") != "pass":
        blockers.append("TDD")
    if lane == "heavy":
        if gates.get("3") != "pass":
            blockers.append("Docs")
        if gates.get("4") not in {"pass", "n/a"}:
            blockers.append("BDD")
        if gates.get("5") != "pass":
            blockers.append("Human attestation")
    if observed_post_validation_failures:
        gate_list = ",".join(str(g) for g in observed_post_validation_failures)
        blockers.append(f"Observed post-validation gate fail: {gate_list}")
    readiness = "ready" if not blockers else "pending"
    return readiness, blockers


def _render_qc_readiness(readiness: str, blockers: list[str]) -> str:
    if readiness == "ready":
        return "[green]READY[/green]"
    return f"[yellow]PENDING[/yellow] (pending: {', '.join(blockers)})"


def _render_pending_checks_cell(blockers: list[str]) -> str:
    """Render compact pending-check codes for table view."""
    if not blockers:
        return "-"

    code_map = {
        "OBPI completion": "O",
        "TDD": "T",
        "Docs": "D",
        "BDD": "B",
        "Human attestation": "H",
    }
    codes: list[str] = []
    for blocker in blockers:
        if blocker.startswith("Observed post-validation gate fail"):
            codes.append("X")
        else:
            codes.append(code_map.get(blocker, "?"))
    return ",".join(codes)


# ---------------------------------------------------------------------------
# Gate and task section renderers
# ---------------------------------------------------------------------------


def _print_status_gate_section(
    info: dict[str, Any],
    lane: str,
    gates: dict[str, str],
    attested: bool,
    attestation_term: Any,
) -> None:
    """Render optional gate-by-gate diagnostics for one ADR.

    When `info["observed_post_validation_gate_failures"]` names a gate, the
    smoothed `pass` cell is annotated with the underlying observation so the
    operator sees both lifecycle authority and live evidence (GHI #411).
    """
    observed = cast(list[int], info.get("observed_post_validation_gate_failures") or [])
    observed_set = set(observed)

    def _annotate(gate_num: int, rendered: str) -> str:
        if gate_num in observed_set:
            return f"{rendered} [yellow](observed FAIL post-validation)[/yellow]"
        return rendered

    console.print("  Gate 1 (ADR):   [green]PASS[/green]")
    console.print(f"  Gate 2 (TDD):   {_annotate(2, _render_gate_status(gates.get('2')))}")
    if lane != "heavy":
        return

    console.print(f"  Gate 3 (Docs):  {_annotate(3, _render_gate_status(gates.get('3')))}")
    console.print(f"  Gate 4 (BDD):   {_annotate(4, _render_gate_status(gates.get('4')))}")
    if gates.get("4") == "n/a":
        console.print(f"                 ({info.get('gate4_na_reason')})")

    if attested:
        detail = f" ({attestation_term})" if attestation_term else ""
        console.print(f"  Gate 5 (Human): [green]PASS[/green]{detail}")
        return
    console.print("  Gate 5 (Human): [yellow]PENDING[/yellow]")


def _print_status_task_section(task_summary: dict[str, Any] | None) -> None:
    """Render task summary for one ADR when tasks exist."""
    if task_summary is None:
        return

    total = task_summary.get("total", 0)
    completed = task_summary.get("completed", 0)
    in_progress = task_summary.get("in_progress", 0)
    pending = task_summary.get("pending", 0)
    blocked = task_summary.get("blocked", 0)
    escalated = task_summary.get("escalated", 0)
    tracing_policy = task_summary.get("tracing_policy", "advisory")

    parts = [f"{completed}/{total} done"]
    if in_progress > 0:
        parts.append(f"{in_progress} active")
    if pending > 0:
        parts.append(f"{pending} pending")
    if blocked > 0:
        parts.append(f"{blocked} blocked")
    if escalated > 0:
        parts.append(f"[red]{escalated} escalated[/red]")

    policy_label = "[bold]required[/bold]" if tracing_policy == "required" else "advisory"
    console.print(f"  Tasks:          {', '.join(parts)} (tracing: {policy_label})")

    per_req = task_summary.get("per_req", {})
    for req_id, counts in sorted(per_req.items()):
        req_total = counts["total"]
        req_completed = counts["completed"]
        req_in_progress = counts.get("in_progress", 0)
        req_parts = [f"{req_completed}/{req_total} done"]
        if req_in_progress > 0:
            req_parts.append(f"{req_in_progress} active")
        req_pending = counts.get("pending", 0)
        if req_pending > 0:
            req_parts.append(f"{req_pending} pending")
        console.print(f"    {req_id}:  {', '.join(req_parts)}")


# ---------------------------------------------------------------------------
# Row and table renderers
# ---------------------------------------------------------------------------


def _render_status_row(
    adr_id: str,
    info: dict[str, Any],
    default_mode: str,
    show_gates: bool,
    *,
    full: bool = False,
) -> None:
    """Render one ADR row for text status output."""
    attested = bool(info.get("attested", False))
    lifecycle_status = info.get("lifecycle_status", "Pending")
    lane = cast(str, info.get("lane", default_mode))
    gates = cast(dict[str, str], info.get("gates", {}))
    attestation_term = info.get("attestation_term")
    obpi_rows = cast(list[dict[str, Any]], info.get("obpis", []))
    obpi_summary = cast(dict[str, Any], info.get("obpi_summary", {}))

    console.print(f"[bold]{adr_id}[/bold] ({lifecycle_status})")
    _print_status_obpi_section(obpi_rows, obpi_summary, full=full)
    _print_status_task_section(info.get("task_summary"))

    observed_post_validation_failures = cast(
        list[int], info.get("observed_post_validation_gate_failures") or []
    )
    qc_readiness, qc_blockers = _qc_readiness(
        gates, lane, obpi_summary, observed_post_validation_failures
    )
    console.print(f"  QC Readiness:   {_render_qc_readiness(qc_readiness, qc_blockers)}")
    if show_gates:
        _print_status_gate_section(info, lane, gates, attested, attestation_term)
    console.print()


def _is_foundation_adr(adr_id: str) -> bool:
    """Return True for foundation ADRs (ADR-0.0.x pattern)."""
    import re  # noqa: PLC0415

    m = re.match(r"^ADR-(\d+)\.(\d+)\.\d+", adr_id)
    return m is not None and m.group(1) == "0" and m.group(2) == "0"


def _render_status_table(
    adrs: dict[str, dict[str, Any]],
    default_mode: str,
    *,
    adr_type: str | None = None,
    full: bool = False,
) -> None:
    """Render ADR status as three tables: Foundation, Features, and Pool.

    When *adr_type* is given (``"foundation"``, ``"feature"``, or ``"pool"``),
    only the matching table is rendered.
    """
    from gzkit.commands.status import _adr_status_sort_key  # noqa: PLC0415

    foundation: list[tuple[str, dict[str, Any]]] = []
    features: list[tuple[str, dict[str, Any]]] = []
    pool: list[tuple[str, dict[str, Any]]] = []
    for adr_id, info in sorted(adrs.items(), key=lambda item: _adr_status_sort_key(item[0])):
        if _is_pool_adr_id(adr_id):
            pool.append((adr_id, info))
        elif _is_foundation_adr(adr_id):
            foundation.append((adr_id, info))
        else:
            features.append((adr_id, info))

    printed = False
    if adr_type in (None, "foundation"):
        _render_adr_table(TABLE_TITLE_FOUNDATION, foundation, default_mode, full=full)
        printed = True
    if adr_type in (None, "feature") and features:
        if printed:
            console.print()
        _render_adr_table(TABLE_TITLE_FEATURE, features, default_mode, full=full)
        printed = True
    if adr_type in (None, "pool") and pool:
        if printed:
            console.print()
        _render_adr_table(TABLE_TITLE_POOL, pool, default_mode, full=full)
    console.print(
        "Checks legend: O=OBPI completion, T=TDD, D=Docs, B=BDD, H=Human attestation, "
        "X=Observed post-validation gate fail"
    )


def _render_adr_table(
    title: str,
    rows: list[tuple[str, dict[str, Any]]],
    default_mode: str,
    *,
    full: bool = False,
) -> None:
    """Render a single ADR status table with the given title and rows.

    When ``full=True`` the ADR column folds long IDs across multiple
    visual lines instead of ellipsizing them — required for attestation
    and bug-report evidence per GHI #319.
    """
    table = Table(title=title, box=box.ROUNDED, padding=(0, 0))
    adr_overflow = "fold" if full else "ellipsis"
    table.add_column("ADR", overflow=adr_overflow)
    table.add_column("Lifecycle", no_wrap=True)
    table.add_column("Lane", no_wrap=True)
    table.add_column("OBPI", justify="right", no_wrap=True)
    table.add_column("Status", no_wrap=True)
    table.add_column("QC", no_wrap=True)
    table.add_column("Checks", no_wrap=True)
    table.row_styles = ["none", "dim"]

    for adr_id, info in rows:
        lane = cast(str, info.get("lane", default_mode))
        gates = cast(dict[str, str], info.get("gates", {}))
        obpi_summary = cast(dict[str, Any], info.get("obpi_summary", {}))
        lifecycle_status = cast(str, info.get("lifecycle_status", "Pending"))

        obpi_total = cast(int, obpi_summary.get("total", 0))
        obpi_completed = cast(int, obpi_summary.get("completed", 0))
        obpi_unit_status = cast(str, obpi_summary.get("unit_status", "unscoped"))
        observed_post_validation_failures = cast(
            list[int], info.get("observed_post_validation_gate_failures") or []
        )
        qc_readiness, qc_blockers = _qc_readiness(
            gates, lane, obpi_summary, observed_post_validation_failures
        )
        qc_label = "READY" if qc_readiness == "ready" else "PENDING"

        table.add_row(
            adr_id,
            lifecycle_status,
            lane.upper(),
            f"{obpi_completed}/{obpi_total}",
            obpi_unit_status.upper(),
            qc_label,
            _render_pending_checks_cell(qc_blockers),
        )

    console.print(table)


# ---------------------------------------------------------------------------
# ADR report renderer
# ---------------------------------------------------------------------------


def _render_adr_report(result: dict[str, Any]) -> None:
    """Render deterministic ASCII table report for a single ADR."""
    from gzkit.commands.status import ADR_SEMVER_STATUS_ID_RE  # noqa: PLC0415

    obpi_summary = cast(dict[str, Any], result.get("obpi_summary", {}))
    obpi_total = cast(int, obpi_summary.get("total", 0))
    obpi_completed = cast(int, obpi_summary.get("completed", 0))
    gates = cast(dict[str, str], result.get("gates", {}))
    lane = cast(str, result.get("lane", "lite"))
    observed_post_validation_failures = cast(
        list[int], result.get("observed_post_validation_gate_failures") or []
    )
    qc_readiness, _qc_blockers = _qc_readiness(
        gates, lane, obpi_summary, observed_post_validation_failures
    )
    closeout_label = "READY" if result.get("closeout_ready") else "BLOCKED"
    qc_label = "READY" if qc_readiness == "ready" else "PENDING"

    # --- ADR Overview ---
    adr_full = cast(str, result["adr"])
    m = ADR_SEMVER_STATUS_ID_RE.match(adr_full)
    adr_short = f"ADR-{m['major']}.{m['minor']}.{m['patch']}" if m else adr_full

    overview = Table(title="ADR Overview", box=box.ROUNDED, padding=(0, 1))
    overview.add_column("ADR", no_wrap=True)
    overview.add_column("Lane", no_wrap=True)
    overview.add_column("Lifecycle", no_wrap=True)
    overview.add_column("Closeout Phase", no_wrap=True)
    overview.add_column("OBPI", no_wrap=True)
    overview.add_column("Closeout", no_wrap=True)
    overview.add_column("QC", no_wrap=True)
    overview.add_row(
        adr_short,
        lane,
        cast(str, result.get("lifecycle_status", "Pending")),
        cast(str, result.get("closeout_phase", "pre_closeout")),
        f"{obpi_completed}/{obpi_total}",
        closeout_label,
        qc_label,
    )
    console.print(overview)

    # --- OBPIs ---
    obpi_rows = cast(list[dict[str, Any]], result.get("obpis", []))
    obpi_table = Table(title="OBPIs", box=box.ROUNDED, padding=(0, 1))
    obpi_table.add_column("#", no_wrap=True)
    obpi_table.add_column("OBPI ID", overflow="fold")
    obpi_table.add_column("State", no_wrap=True)
    obpi_table.add_column("Brief", no_wrap=True)
    obpi_table.add_column("Done", no_wrap=True)
    for idx, row in enumerate(obpi_rows, 1):
        obpi_table.add_row(
            f"{idx:02d}",
            cast(str, row.get("id", "")),
            cast(str, row.get("runtime_state", "pending")),
            cast(str, row.get("brief_status", "draft")),
            "yes" if row.get("completed") else "no",
        )
    console.print(obpi_table)

    # --- Withdrawn disclosure ---
    # Withdrawn OBPIs are intentionally excluded from the active table above, but
    # silent omission makes the resulting id gaps read as dropped data. Disclose
    # their existence (ledger history preserved) and point to the disposition.
    withdrawn_obpis = cast(list[str], result.get("withdrawn_obpis", []))
    if withdrawn_obpis:
        console.print(
            f"Withdrawn (hidden): {len(withdrawn_obpis)} OBPI(s) excluded above; "
            "ledger history preserved. See the ADR Checklist for disposition."
        )

    # --- Issues ---
    issues: list[str] = []
    for idx, row in enumerate(obpi_rows, 1):
        prefix = f"{idx:02d}"
        for issue in cast(list[str], row.get("issues", [])):
            issues.append(f"{prefix} {issue}")
        for issue in cast(list[str], row.get("reflection_issues", [])):
            issues.append(f"{prefix} {issue}")
    if issues:
        console.print("Issues")
        for line in issues:
            console.print(line)

    # --- Closeout Blockers (specific reasons beyond the per-OBPI Issues list) ---
    closeout_blockers = cast(list[str], result.get("closeout_blockers", []))
    if closeout_blockers:
        console.print(f"Closeout Readiness: {closeout_label}")
        console.print("Closeout Blockers:")
        for blocker in closeout_blockers:
            console.print(f"  - {blocker}")

    # --- QC Pending Checkpoints (specific gates that still need to pass) ---
    if qc_readiness != "ready" and _qc_blockers:
        console.print(f"QC Readiness: {qc_label} (pending: {', '.join(_qc_blockers)})")
