#!/usr/bin/env python3
"""TESTING.md doc↔code lint — the tier-drift class dies here (M2 Wave-2 §1.3).

The M2 Wave-1 recon found THREE different answers for `integration`'s
tier (§13.1 budget note vs §13.11 table vs `mgf.common.testing.
MARKER_TIER`), a phantom `functional` marker documented-but-registered-
nowhere, and a zombie `slow` — because nothing pinned the DOC against
the CODE. This gate parses TESTING.md's §13.11 inline `MARKER_TIER`
block + taxonomy table (+ §19.2 tier budgets) and fails on drift.

Two enforcement layers (never-block by design):

1. **Doc-internal consistency** — always runs, zero dependencies:
   the inline block must parse, match the §13.11 table's markers +
   tiers exactly, contain exactly the twelve canonical markers, and
   never re-grow `slow` / `functional`.
2. **Doc↔code comparison** — arms when `mgf.common.testing` is
   importable. mgf-standard itself is a PURE LEAF (it deliberately
   declines the mgf-* dependency cycle — see pyproject), so in THIS
   repo's CI only layer 1 runs; the code side of the same pin lives
   in mgf-common (`tests/unit/testing/test_testing_doc_alignment.py`
   reads the installed corpus via `mgf.standard.read_standard`), so
   the comparison is enforced two-sided across the fleet exactly like
   the supervisor↔schema alignment suites.

Invoked by ``gen_rules_index.py --check`` (which rides the pytest
steps — the same channel as the rules-index gate); also runnable
directly: ``python scripts/check_testing_doc.py [--doc PATH]``.
"""

from __future__ import annotations

import argparse
import ast
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
DOC = ROOT / "docs" / "standards" / "TESTING.md"

# The canonical twelve (M2 Wave-2 tier sitting). The doc-internal
# layer pins the doc against THIS list; the code layer then pins the
# list against mgf.common.testing.MARKER_TIER when importable — so a
# code change without a doc+lint change (or vice versa) goes red
# somewhere, always.
CANONICAL_TWELVE: dict[str, int] = {
    "smoke": 0,
    "contract": 1,
    "property": 1,
    "regression": 1,
    "fuzz": 1,
    "conformance": 2,
    "use_case": 2,
    "concurrency": 2,
    "integration": 2,
    "perf": 3,
    "stress": 3,
    "e2e": 3,
}

# Markers that must NEVER return: `slow` completed its removal cycle;
# `functional` was a doc-only phantom no code or consumer ever
# registered (struck by the Wave-2 tier sitting).
FORBIDDEN_MARKERS = ("slow", "functional")

_BLOCK_RE = re.compile(r"MARKER_TIER = (\{.*?\})", re.DOTALL)
# NB [a-z0-9_]: `e2e` carries a digit — a pure-alpha class silently
# drops its row (caught by this gate's own first run).
_TABLE_ROW_RE = re.compile(r"^\| `([a-z0-9_]+)` \| [^|]+ \| tier-(\d) \|", re.MULTILINE)


def _parse_block(text: str, errors: list[str]) -> dict[str, int] | None:
    m = _BLOCK_RE.search(text)
    if not m:
        errors.append("TESTING.md: the §13.11 inline `MARKER_TIER = {...}` block is missing")
        return None
    try:
        parsed = ast.literal_eval(m.group(1))
    except (ValueError, SyntaxError) as e:
        errors.append(f"TESTING.md: the inline MARKER_TIER block does not parse: {e}")
        return None
    if not isinstance(parsed, dict) or not all(
        isinstance(k, str) and isinstance(v, int) for k, v in parsed.items()
    ):
        errors.append("TESTING.md: the inline MARKER_TIER block is not a dict[str, int]")
        return None
    return parsed


def _parse_table(text: str) -> dict[str, int]:
    """§13.11 taxonomy-table rows: marker → tier (skips the unmarked row)."""
    return {m: int(t) for m, t in _TABLE_ROW_RE.findall(text)}


def check(doc_path: Path | None = None) -> int:
    """Run the gate. Returns 0 green / 1 drift (printed to stderr)."""
    path = doc_path or DOC
    try:
        text = path.read_text(encoding="utf-8")
    except OSError as e:
        print(f"TESTING-DOC GATE: cannot read {path}: {e}", file=sys.stderr)
        return 1
    errors: list[str] = []

    block = _parse_block(text, errors)
    table = _parse_table(text)

    if block is not None:
        # Layer 1a — the block IS the canonical twelve.
        if block != CANONICAL_TWELVE:
            errors.append(
                "inline MARKER_TIER block drifted from the canonical twelve:\n"
                f"    doc block: {block}\n"
                f"    canonical: {CANONICAL_TWELVE}"
            )
        for bad in FORBIDDEN_MARKERS:
            if bad in block:
                errors.append(
                    f"forbidden marker {bad!r} re-entered the inline MARKER_TIER block "
                    "(removed by the M2 Wave-2 tier sitting — do not re-add)"
                )
        # Layer 1b — the taxonomy table agrees with the block.
        table_mapped = {m: t for m, t in table.items() if m in block}
        if table_mapped != block:
            only_block = {m: v for m, v in block.items() if table.get(m) != v}
            errors.append(
                "§13.11 table vs inline MARKER_TIER block drift "
                f"(marker → (table, block)): "
                f"{ {m: (table.get(m), v) for m, v in only_block.items()} }"
            )
    for bad in FORBIDDEN_MARKERS:
        if bad in table:
            errors.append(f"forbidden marker {bad!r} re-entered the §13.11 taxonomy table")

    # Layer 2 — doc↔code, when the code is importable (not in
    # mgf-standard's own pure-leaf venv; armed in any env that has
    # mgf-common — the mirror test in mgf-common always arms).
    code_checked = False
    try:
        from mgf.common import testing as mct  # type: ignore[import-not-found]
    except ImportError:
        mct = None
    if mct is not None and block is not None:
        code_checked = True
        if dict(mct.MARKER_TIER) != block:
            errors.append(
                "doc↔code drift: mgf.common.testing.MARKER_TIER != the doc's inline block:\n"
                f"    code: {dict(mct.MARKER_TIER)}\n"
                f"    doc:  {block}"
            )
        for tier in mct.TIERS:
            if not re.search(
                rf"{re.escape(tier.name)}\b.*?{tier.per_test_seconds:.2f}", text, re.DOTALL
            ):
                errors.append(
                    f"doc↔code drift: §19.2 no longer states {tier.name}'s "
                    f"per-test budget {tier.per_test_seconds:.2f}s"
                )

    if errors:
        print("TESTING-DOC GATE: RED — doc↔code tier truth drifted:", file=sys.stderr)
        for e in errors:
            print(f"  - {e}", file=sys.stderr)
        return 1
    scope = "doc-internal + code compare" if code_checked else "doc-internal (mgf.common absent)"
    print(
        f"TESTING-DOC GATE: GREEN — twelve-marker taxonomy consistent ({scope}); "
        f"{len(table)} table rows checked."
    )
    return 0


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description="TESTING.md doc↔code tier-truth gate.")
    ap.add_argument(
        "--doc",
        type=Path,
        default=None,
        help="Path to a TESTING.md to check (default: the repo's canonical copy). "
        "Used by the seeded-negative tests.",
    )
    args = ap.parse_args(argv)
    return check(args.doc)


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