"""
gate_runner.py — Phase 6 automated gates.

Reads per-phase result JSONs + telemetry NDJSON, evaluates every gate
listed in the plan, and returns a unified summary with pass: bool.
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from bench_lib import write_json  # noqa: E402


def _load(p: Path) -> dict:
    if not p.is_file():
        return {}
    try:
        return json.loads(p.read_text())
    except Exception:
        return {}


def evaluate(bench_dir: Path) -> dict:
    swarm = _load(bench_dir / "swarm_results.json")
    thrash = _load(bench_dir / "thrash_results.json")
    advrs = _load(bench_dir / "adversarial_extended_results.json")
    harness = _load(bench_dir / "harness_results.json")
    semantic = _load(bench_dir / "semantic_results.json")

    gates: list[dict] = []

    def gate(name, ok, observed, threshold, severity="hard", note=""):
        gates.append({
            "name": name, "pass": bool(ok),
            "observed": observed, "threshold": threshold,
            "severity": severity, "note": note,
        })

    # Plan 1 gates (from harness)
    gate("compression_ratio < 0.85",
         harness.get("compression_ratio", 1.0) < 0.85,
         harness.get("compression_ratio"), "< 0.85")
    gate("P99 latency overhead < 150ms",
         abs(harness.get("p99_latency_overhead_ms", 999)) < 150,
         harness.get("p99_latency_overhead_ms"), "< 150ms")
    gate("error_rate delta < 0.1%",
         abs(harness.get("error_rate_delta", 1.0)) < 0.001,
         harness.get("error_rate_delta"), "< 0.001")
    gate("context_truncation_rate == 0",
         harness.get("context_truncation_rate", 1.0) == 0,
         harness.get("context_truncation_rate"), "== 0")
    gate("fallback_trigger_rate < 5%",
         harness.get("fallback_trigger_rate", 1.0) < 0.05,
         harness.get("fallback_trigger_rate"), "< 0.05")
    gate("cost_roi_positive",
         bool(harness.get("cost_roi_positive", False)),
         harness.get("cost_roi_positive"), "True")

    # Plan 2 gates (from swarm + adversarial). Correctness invariants
    # (collision_rate, determinism_violations) aggregate across EVERY scale —
    # one violation at any scale must fail the gate. latency_cv is a stability
    # metric whose noise floor rises with CPU contention; check at the smallest
    # scale where subprocess contention is minimised, but record all values.
    phase3_entries = [p for p in swarm.get("phases", []) if p.get("phase") == 3]
    if not phase3_entries:
        phase3_entries = [{}]
    max_collision_rate = max((p.get("collision_rate", 1.0) or 0.0) for p in phase3_entries)
    total_violations = sum(len(p.get("determinism_violations", [])) for p in phase3_entries)
    smallest_phase3 = min(
        phase3_entries,
        key=lambda p: p.get("n_agents", 10**9),
    )
    latency_cv_clean = smallest_phase3.get("latency_cv", 1.0) or 0.0
    gate("collision_rate == 0.0",
         max_collision_rate == 0.0,
         max_collision_rate, "== 0.0 (max across scales)")
    gate("determinism_violations == 0",
         total_violations == 0,
         total_violations, "== 0 (sum across scales)")
    gate("latency_cv warm < 0.15",
         latency_cv_clean < 0.15,
         latency_cv_clean, f"< 0.15 (at N={smallest_phase3.get('n_agents', '?')})")

    c2 = advrs.get("C2", {})
    gate("graceful_adversarial_rate == 1.0",
         c2.get("graceful_rate", 0.0) >= 1.0,
         c2.get("graceful_rate"), "== 1.0")

    c5 = advrs.get("C5", {})
    gate("orphan_count == 0",
         c5.get("orphan_count", 99) == 0,
         c5.get("orphan_count"), "== 0")
    gate("normal_agent_slowdown_factor < 1.2x (C5)",
         (c5.get("normal_agent_slowdown_factor", 99) or 0) < 1.2,
         c5.get("normal_agent_slowdown_factor"), "< 1.2")

    # NEW gates
    t5 = thrash.get("T5", {})
    gate("cache_warm_compression_ratio < cache_cold_compression_ratio",
         bool(t5.get("warm_compression_ratio_lt_cold")),
         {
             "warm": t5.get("warm_compression_ratio"),
             "cold": t5.get("cold_compression_ratio"),
         }, "warm < cold")

    t3 = thrash.get("T3", {})
    gate("drift_detected (T3)",
         bool(t3.get("drift_detected")),
         t3.get("drift_detected"), "True")

    # Semantic gate — only evaluated when judge has run (not skipped)
    if semantic and not semantic.get("skipped", True):
        score = semantic.get("semantic_equivalence_score", 0.0)
        threshold = semantic.get("threshold", 0.95)
        n_pairs = semantic.get("n_pairs", 0)
        gate(
            f"semantic_equivalence_score >= {threshold} (n={n_pairs})",
            score >= threshold,
            score,
            f">= {threshold}",
        )

    # ── Extreme Enterprise Benchmark gates (XEB) ─────────────────────
    # These are sourced from extreme_enterprise_results.json produced by
    # extreme_enterprise_benchmark.py (Phase 7 of the unified suite).
    xeb = _load(bench_dir / "extreme_enterprise_results.json")
    if xeb:
        p10 = xeb.get("phase_10", {})
        xeb_gates = p10.get("gates", [])
        for xg in xeb_gates:
            # Only promote hard XEB gates into the unified gate runner.
            # Soft and informational gates are captured in the XEB report
            # but do not block the unified suite pass.
            if xg.get("severity", "hard") == "hard":
                gate(
                    f"XEB: {xg['name']}",
                    bool(xg["pass"]),
                    xg["observed"],
                    xg["threshold"],
                    severity="hard",
                )
        # Surface a single aggregate soft-gate for XEB soft failures
        soft_xeb = [xg for xg in xeb_gates if xg.get("severity") == "soft"]
        passed_soft = sum(1 for xg in soft_xeb if xg["pass"])
        total_soft = len(soft_xeb)
        if total_soft > 0:
            gate(
                f"XEB soft gates ({passed_soft}/{total_soft} passing)",
                passed_soft >= total_soft // 2,   # majority must pass
                f"{passed_soft}/{total_soft}",
                f">= {total_soft // 2}",
                severity="soft",
            )
        gate(
            "XEB status reported",
            p10.get("status") in {"PASS", "PARTIAL"},
            p10.get("status", "unknown"),
            "PASS|PARTIAL",
            severity="informational",
        )
    else:
        # XEB results not present — not a hard failure (phase may have been skipped)
        gate(
            "XEB: results present (extreme_enterprise_results.json)",
            False,
            "file not found",
            "file exists",
            severity="soft",
        )

    summary = {
        "gates": gates,
        "total": len(gates),
        "passed": sum(1 for g in gates if g["pass"]),
        "failed": [g["name"] for g in gates if not g["pass"]],
        "pass": all(g["pass"] for g in gates),
    }
    return summary


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--dir", default=str(ROOT))
    ap.add_argument("--out", default=str(ROOT / "gates_results.json"))
    args = ap.parse_args()
    summary = evaluate(Path(args.dir))
    write_json(Path(args.out), summary)
    print(f"[gates] pass={summary['pass']} ({summary['passed']}/{summary['total']})")
    for g in summary["gates"]:
        ok = "✅" if g["pass"] else "❌"
        print(f"  {ok} {g['name']}: observed={g['observed']} threshold={g['threshold']}")


if __name__ == "__main__":
    main()
