#!/usr/bin/env python3
"""Cross-check a project's PRESENTATION_LAYER (PR-*) conformance
tracker against the live ``import-linter`` output.

A project's ``docs/inprogress/MGF_STANDARDS_CONFORMANCE.md`` says
"PR-01 ✅" → the import-linter contract enforcing PR-01 is
expected to be KEPT in CI. If the tracker says ✅ but lint-imports
reports the contract as BROKEN (or the contract is missing), the
tracker is lying — a regression has slipped past whoever last
edited the tracker.

This script parses both signals + reports drift, so a federation
sibling pair adopting the standard can wire it into CI as a
guard against the most common failure mode: a contributor merging
a violating change + the conformance row staying ✅.

Usage:
    audit_pr_conformance.py <project-root>

Exit codes:
    0 — tracker and import-linter agree
    1 — drift detected (tracker says ✅ but contract is BROKEN or absent)
    2 — usage error (tracker file missing, lint-imports not runnable)

The check is conservative: tracker rows marked 🚫 (decline) /
N/A / ⏳ (defer) are not validated against import-linter — they
declare the rule doesn't apply. Only ✅ rows are cross-checked.
"""

from __future__ import annotations

import re
import subprocess
import sys
from pathlib import Path

# Tracker rows look like:
#   | PR-01 SDK ... | ✅ | ... |
#   | PR-04 ... | ✅ | enforced via lint-imports ... |
_TRACKER_ROW_RE = re.compile(
    r"^\|\s*(?P<id>PR-\d+)\b[^|]*\|\s*(?P<status>[^|]+?)\s*\|",
)

# Status markers we recognise (audit-outcomes taxonomy).
_STATUS_PASS = {"✅"}
_STATUS_DECLINE_OR_DEFER = {"🚫", "N/A", "n/a", "⏳", "⚪"}
_STATUS_FAIL = {"❌"}


def _read_tracker(repo: Path) -> dict[str, str]:
    """Return {PR-NN: status-marker} from the project's conformance tracker.

    Reads the §PR section by finding rows that begin with a `| PR-NN`
    cell. Conservative parser — only consumes well-formed markdown
    table rows; ignores everything else in the file.
    """
    candidates = (
        repo / "docs/inprogress/MGF_STANDARDS_CONFORMANCE.md",
        repo / "docs/MGF_STANDARDS_CONFORMANCE.md",
    )
    tracker = next((p for p in candidates if p.exists()), None)
    if tracker is None:
        raise FileNotFoundError(
            f"no MGF_STANDARDS_CONFORMANCE.md in {repo}",
        )
    rows: dict[str, str] = {}
    for line in tracker.read_text(encoding="utf-8").splitlines():
        m = _TRACKER_ROW_RE.match(line)
        if not m:
            continue
        pr_id = m.group("id")
        status_cell = m.group("status").strip()
        # Pick the first known marker in the status cell — handles
        # rows like "✅ (consumer side)" or "✅-alternate-pattern".
        first_marker = ""
        for marker in (*_STATUS_PASS, *_STATUS_DECLINE_OR_DEFER, *_STATUS_FAIL):
            if marker in status_cell:
                first_marker = marker
                break
        rows[pr_id] = first_marker or status_cell
    return rows


def _run_lint_imports(repo: Path) -> tuple[bool, str]:
    """Invoke lint-imports for the project; return (all_kept, raw_output)."""
    binary = repo / ".venv/bin/lint-imports"
    if not binary.exists():
        return (
            False,
            f"lint-imports not found at {binary} — run `uv sync` first.",
        )
    try:
        proc = subprocess.run(
            [str(binary), "--config", "pyproject.toml"],
            cwd=repo,
            capture_output=True,
            text=True,
            timeout=60,
        )
    except subprocess.SubprocessError as exc:
        return False, f"failed to invoke lint-imports: {exc}"
    return proc.returncode == 0, proc.stdout + proc.stderr


def main(argv: list[str]) -> int:
    if len(argv) != 2:
        print(__doc__, file=sys.stderr)
        return 2
    repo = Path(argv[1]).resolve()
    if not repo.is_dir():
        print(f"ERROR: {repo} is not a directory", file=sys.stderr)
        return 2

    try:
        rows = _read_tracker(repo)
    except FileNotFoundError as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 2

    pr_passing = sorted(k for k, v in rows.items() if v in _STATUS_PASS)
    pr_other = sorted(k for k, v in rows.items() if v not in _STATUS_PASS)

    print(f"audit_pr_conformance: {repo}")
    print(f"  ✅ rows: {len(pr_passing)} ({', '.join(pr_passing)})")
    print(f"  other rows: {len(pr_other)} ({', '.join(pr_other)})")

    all_kept, output = _run_lint_imports(repo)

    # Drift detection: if any row says ✅, lint-imports MUST be kept.
    # We don't try to map individual PR-NN rows to specific contracts
    # (contract names are repo-defined); we use the broad signal "any
    # contract broken AND any ✅ claimed" as the lie-test.
    if pr_passing and not all_kept:
        print(
            "\nFAIL — tracker claims at least one PR-* row is ✅ but "
            "lint-imports reports a broken contract:",
            file=sys.stderr,
        )
        print(output, file=sys.stderr)
        return 1

    if not pr_passing and not all_kept:
        print(
            "\nWARN — lint-imports has broken contracts AND no PR-* "
            "row is ✅. Honest, but the project has a real layering "
            "violation to fix.",
            file=sys.stderr,
        )
        return 1

    print("\nOK — tracker and import-linter agree.")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
