#!/usr/bin/env python
"""Prototype of the IR-21 (c) enforcement check.

Answers: for every test consumer of a fixture [[bin]], is that bin's BUILD
guaranteed? Guaranteed iff the consumer is an integration test/bench of the
SAME package that declares the bin (cargo builds all of a package's bins for
any integration test of that package). Otherwise the build must be guaranteed
explicitly by a prebuild in CI, or it is a red.

No compile: `cargo metadata --no-deps` + a tracked-file scan only.

--no-prebuilds  empty the prebuild allowlist (negative control: every
                not-guaranteed site must go red, guaranteed sites must NOT).
"""
import json
import re
import subprocess
import sys
from pathlib import Path

ROOT = Path(subprocess.run(["git", "rev-parse", "--show-toplevel"],
                           capture_output=True, text=True, check=True).stdout.strip())
NO_PREBUILDS = "--no-prebuilds" in sys.argv

# ---- 1. bin targets + owning package, from metadata (no compile) ----
meta = json.loads(subprocess.run(
    ["cargo", "metadata", "--no-deps", "--format-version", "1"],
    capture_output=True, text=True, check=True, cwd=ROOT).stdout)

bin_owner = {}      # bin name -> package name
pkg_dir = {}        # package name -> manifest dir (relative, posix)
for p in meta["packages"]:
    d = Path(p["manifest_path"]).parent
    try:
        pkg_dir[p["name"]] = d.relative_to(ROOT).as_posix()
    except ValueError:
        pkg_dir[p["name"]] = d.as_posix()
    for t in p["targets"]:
        if "bin" in t["kind"]:
            bin_owner[t["name"]] = p["name"]

# ---- 2. tracked files ----
tracked = subprocess.run(["git", "ls-files"], capture_output=True, text=True,
                         check=True, cwd=ROOT).stdout.splitlines()

# ---- 3. declared prebuilds in CI ----
prebuilds = set()
if not NO_PREBUILDS:
    pb = re.compile(r"cargo build\s+-p\s+([\w-]+)\s+--bin\s+([\w-]+)")
    for f in tracked:
        if f.startswith(".github/"):
            for m in pb.finditer((ROOT / f).read_text(encoding="utf-8", errors="replace")):
                prebuilds.add(m.group(2))

# ---- 4. consumer sites ----
site_re = re.compile(r'sibling_bin\("([\w-]+)"|CARGO_BIN_EXE_([\w-]+)')


def owning_pkg(relpath):
    best, best_len = None, -1
    for name, d in pkg_dir.items():
        if relpath.startswith(d + "/") and len(d) > best_len:
            best, best_len = name, len(d)
    return best


def kind_of(relpath):
    # integration test / bench targets of a package: <pkgdir>/tests/** or /benches/**
    parts = relpath.split("/")
    if "tests" in parts or "benches" in parts:
        return "integration"
    if "/src/" in relpath or relpath.endswith("/src/lib.rs"):
        return "unit"
    return "other"


guaranteed, needs, reds = [], [], []
for f in tracked:
    if not f.endswith(".rs"):
        continue
    text = (ROOT / f).read_text(encoding="utf-8", errors="replace")
    for i, line in enumerate(text.splitlines(), 1):
        # Line comments (`//`, `///`, `//!`) are prose, not consumers. Fixture
        # headers legitimately NAME other packages' bins; flagging those is the
        # checker committing the entry's own conflation. Block comments are a
        # known gap in this prototype.
        if line.lstrip().startswith("//"):
            continue
        for m in site_re.finditer(line):
            name = m.group(1) or m.group(2)
            if name not in bin_owner:
                continue            # e.g. CARGO_BIN_EXE_spt in a fixture doc comment
            consumer_pkg = owning_pkg(f)
            k = kind_of(f)
            same = consumer_pkg == bin_owner[name]
            site = (f, i, name, bin_owner[name], consumer_pkg, k)
            if same and k == "integration":
                guaranteed.append(site)
            else:
                needs.append(site)
                if name not in prebuilds:
                    reds.append(site)

print(f"bin targets: {len(bin_owner)}   consumer sites: {len(guaranteed) + len(needs)}")
print(f"  GUARANTEED (same-pkg integration test): {len(guaranteed)}")
print(f"  NEEDS EXPLICIT BUILD:                   {len(needs)}")
print(f"  declared prebuilds: {sorted(prebuilds) if prebuilds else '(none - negative control)'}")
print(f"  RED (needs a build, has no prebuild):   {len(reds)}")
for f, i, name, owner, cpkg, k in reds[:8]:
    print(f"    {f}:{i}  {name} (owned by {owner}, consumer {cpkg}, {k})")
if len(reds) > 8:
    print(f"    ... {len(reds) - 8} more")

# --- false-positive arm: the 11 same-package fixture sites established this
# morning as ALREADY build-guaranteed must never appear in `needs`/`reds`. ---
SAME_PKG_FIXTURES = {"translate_proof_fixture", "git_fixture",
                     "gh_fixture", "post_step_fixture"}
g11 = [s for s in guaranteed if s[2] in SAME_PKG_FIXTURES]
n11 = [s for s in needs if s[2] in SAME_PKG_FIXTURES]
print(f"\nFALSE-POSITIVE ARM (the 11 same-package sites):")
print(f"  classified GUARANTEED: {len(g11)}   wrongly flagged: {len(n11)}")
for s in n11:
    print(f"    FALSE POSITIVE {s[0]}:{s[1]} {s[2]}")
sys.exit(1 if reds else 0)
