#!/usr/bin/env python
"""Per-job mapping for IR-21 (c): for every narrow cargo invocation in CI, is the
cross-package fixture bin its tests consume actually built BEFORE it, in that job?

Converts the repo-wide smoke alarm into a per-job verdict. Three ways a bin can be
guaranteed at a step:
  WORKSPACE  an earlier step in the same job ran a workspace-shaped build
             (emits all plain binaries -- measured this morning)
  PREBUILD   an earlier step ran `cargo build -p X --bin <thatbin>`
  (none)     nothing in the job builds it -> a phantom red on a clean pool
"""
import json
import re
import subprocess
import sys
from pathlib import Path

import yaml

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

# ---- consumed-bin map: test target -> cross-package bins it consumes ----
meta = json.loads(subprocess.run(
    ["cargo", "metadata", "--no-deps", "--format-version", "1"],
    capture_output=True, text=True, check=True, cwd=ROOT).stdout)
bin_owner, pkg_dir = {}, {}
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"]

site_re = re.compile(r'sibling_bin\("([\w-]+)"|CARGO_BIN_EXE_([\w-]+)')
tracked = subprocess.run(["git", "ls-files"], capture_output=True, text=True,
                         check=True, cwd=ROOT).stdout.splitlines()


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


# test-target name -> set of cross-package bins ; and pkg -> those targets
target_needs, pkg_targets = {}, {}
for f in tracked:
    if not f.endswith(".rs") or "/tests/" not in f:
        continue
    rel = f.split("/tests/")[1]
    if "/" in rel:            # tests/fixtures/*, tests/support/* are not targets
        continue
    tgt = rel[:-3]
    cpkg = owning_pkg(f)
    needs = set()
    for line in (ROOT / f).read_text(encoding="utf-8", errors="replace").splitlines():
        if line.lstrip().startswith("//"):
            continue
        for m in site_re.finditer(line):
            name = m.group(1) or m.group(2)
            if name in bin_owner and bin_owner[name] != cpkg:
                needs.add(name)
    target_needs[(cpkg, tgt)] = needs
    pkg_targets.setdefault(cpkg, []).append(tgt)

# ---- unit-test consumers: src/**.rs. Unit tests get NO CARGO_BIN_EXE_* at all,
# not even for their own package's bins, so EVERY bin they name must be built
# explicitly. This is the population `-E 'kind(lib)+kind(bin)'` runs. ----
unit_needs = set()
for f in tracked:
    if not f.endswith(".rs") or "/src/" not in f:
        continue
    for line in (ROOT / f).read_text(encoding="utf-8", errors="replace").splitlines():
        if line.lstrip().startswith("//"):
            continue
        for m in site_re.finditer(line):
            name = m.group(1) or m.group(2)
            if name in bin_owner:
                unit_needs.add(name)

# ---- walk the workflows ----
CARGO = re.compile(r"cargo\s+(?:\+\S+\s+)?(build|test|nextest run|check)([^\n&|;]*)")
PREBUILD = re.compile(r"cargo build\s+-p\s+[\w-]+\s+--bin\s+([\w-]+)")
NARROW = ("-p ", "--bin", "--test ", "--lib", "--bins")

rows = []
for wf in [f for f in tracked if f.startswith(".github/workflows/")]:
    doc = yaml.safe_load((ROOT / wf).read_text(encoding="utf-8", errors="replace"))
    for jobname, job in (doc.get("jobs") or {}).items():
        built = set()          # bins guaranteed so far in this job
        workspace_built = False
        for step in (job.get("steps") or []):
            run = step.get("run") or ""
            for pb in PREBUILD.finditer(run):
                built.add(pb.group(1))
            for m in CARGO.finditer(run):
                verb, args = m.group(1), m.group(2)
                narrow = any(k in args for k in NARROW)
                # A workspace-shaped command emits the plain binaries ONLY if it
                # actually builds integration-test targets. `--workspace` filtered
                # to kind(lib)/kind(bin) does NOT -- it compiles lib/bin test
                # harnesses and emits no plain fixture exe on a clean pool. That is
                # exactly what ci.yml:102-106 documents and prebuilds around, and
                # crediting it here produced a GREEN over the one known-real gap.
                kind_filtered = bool(re.search(r"-E\s*'[^']*'", args)) and \
                    not re.search(r"kind\((test|bench)\)", args)
                unit_step = False
                if not narrow and verb in ("build", "test", "nextest run"):
                    if not kind_filtered:
                        workspace_built = True
                        continue
                    unit_step = True     # a lib/bin-kind run: CONSUMER, not builder
                elif narrow and re.search(r"--lib|--bins", args):
                    unit_step = True
                # `--bins` contains `--bin`; only a single-bin BUILD is a prebuild.
                if verb == "check" or re.search(r"--bin\s", args):
                    continue
                if not narrow and not unit_step:
                    continue
                if unit_step:
                    need = set(unit_needs)
                else:
                    # which package / targets does this narrow step run?
                    pm = re.search(r"-p\s+([\w-]+)", args)
                    tm = re.findall(r"--test\s+([\w-]+)", args)
                    pkg = pm.group(1) if pm else None
                    if tm and pkg:
                        tgts = tm
                    elif pkg:
                        tgts = pkg_targets.get(pkg, [])
                    else:
                        continue
                    need = set()
                    for t in tgts:
                        need |= target_needs.get((pkg, t), set())
                if not need:
                    continue
                missing = {b for b in need if b not in built} if not workspace_built else set()
                rows.append((wf.split("/")[-1], jobname,
                             ("cargo " + verb + args).strip()[:58],
                             sorted(need),
                             "WORKSPACE" if workspace_built
                             else ("PREBUILD" if not missing else "NONE"),
                             sorted(missing)))

print(f"{'workflow':<12} {'job':<22} {'invocation':<40} {'needs':<26} verdict")
print("-" * 118)
gaps = 0
for wf, job, inv, need, verdict, missing in rows:
    n = ",".join(need)
    print(f"{wf:<12} {job:<22} {inv[:38]:<40} {n[:24]:<26} {verdict}"
          + (f"  MISSING={','.join(missing)}" if missing else ""))
    if verdict == "NONE":
        gaps += 1
print(f"\nnarrow invocations consuming cross-package bins: {len(rows)}   UNGUARANTEED: {gaps}")
sys.exit(1 if gaps else 0)
