#!/usr/bin/env python3
"""Mugi 1.0 release gate — one command whose green means safe to ship.

Runs static gates (smoke, kernel boundary, native tool calls, SwiftUI lint,
safe-defaults review) then live kernel batteries in --gate mode when a backend
is listening, then Swift unit tests. Live runs before xcodebuild because the
test host launches Mugi and kills a standalone ``make backend`` process.
Flaky and monitor-only outcomes are reported but non-gating.

Usage:
  ~/.mugi/venv/bin/python scripts/release_gate.py
  ~/.mugi/venv/bin/python scripts/release_gate.py --skip-live   # static only
  ~/.mugi/venv/bin/python scripts/release_gate.py --json /tmp/release_gate.json

Requires a listening mugi_backend for the live phase (``make backend`` or the
Swift app). The live phase uses the deterministic gate pillar subset.
"""
from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
import time
from typing import Any

_REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VENV_PYTHON = os.path.join(os.path.expanduser("~"), ".mugi", "venv", "bin", "python")

# Deterministic live gate subset (no full agent soak).
AGENT_GATE_PILLARS = "recovery,user_trust,chat_core,sidebar,preferences"


def _run(cmd: list[str], *, label: str, cwd: str | None = None) -> dict[str, Any]:
    print(f"\n==> {label}", flush=True)
    print(f"    {' '.join(cmd)}", flush=True)
    t0 = time.monotonic()
    proc = subprocess.run(
        cmd,
        cwd=cwd or _REPO,
        capture_output=True,
        text=True,
    )
    elapsed = time.monotonic() - t0
    out = (proc.stdout or "") + (proc.stderr or "")
    if proc.stdout:
        print(proc.stdout, end="" if proc.stdout.endswith("\n") else "\n")
    if proc.stderr:
        print(proc.stderr, end="" if proc.stderr.endswith("\n") else "\n", file=sys.stderr)
    ok = proc.returncode == 0
    print(f"    {'PASS' if ok else 'FAIL'} ({elapsed:.1f}s)", flush=True)
    return {
        "label": label,
        "cmd": cmd,
        "ok": ok,
        "exit_code": proc.returncode,
        "elapsed_sec": round(elapsed, 2),
        "output_tail": out[-4000:] if out else "",
    }


def _backend_healthy(port: int = 8321) -> bool:
    try:
        import urllib.request

        with urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=4) as resp:
            return resp.status == 200
    except Exception:
        return False


def main() -> int:
    parser = argparse.ArgumentParser(description="Mugi release gate")
    parser.add_argument(
        "--skip-live",
        action="store_true",
        help="Run static gates only (no live batteries).",
    )
    parser.add_argument(
        "--skip-swift",
        action="store_true",
        help="Skip xcodebuild Swift unit tests (faster local iteration).",
    )
    parser.add_argument("--json", default="", help="Write JSON report to path")
    parser.add_argument("--backend-port", type=int, default=8321)
    args = parser.parse_args()

    py = VENV_PYTHON if os.path.isfile(VENV_PYTHON) else sys.executable
    steps: list[dict[str, Any]] = []

    steps.append(_run(["make", "-s", "smoke"], label="smoke"))
    steps.append(_run(["make", "-s", "check-kernel-boundary"], label="check-kernel-boundary"))
    steps.append(_run(["make", "-s", "check-native-tool-calls"], label="check-native-tool-calls"))
    steps.append(_run(["make", "-s", "swiftui-lint"], label="swiftui-lint"))
    steps.append(_run([py, "scripts/review_safe_defaults.py"], label="review-safe-defaults"))

    if not args.skip_live:
        if not _backend_healthy(args.backend_port):
            steps.append({
                "label": "backend-health",
                "ok": False,
                "exit_code": 2,
                "elapsed_sec": 0,
                "output_tail": (
                    f"No healthy backend on port {args.backend_port}. "
                    "Start with `make backend` or launch the Swift app."
                ),
            })
        else:
            steps.append({
                "label": "backend-health",
                "ok": True,
                "exit_code": 0,
                "elapsed_sec": 0,
                "output_tail": f"health:200 port={args.backend_port}",
            })
            live_cmds = [
                (
                    "live-recovery-gate",
                    [
                        py,
                        "scripts/kernel_live_agent_battery.py",
                        "--reset-board",
                        "--pillar",
                        "recovery",
                        "--gate",
                        "--delay",
                        "3",
                    ],
                ),
                (
                    "live-user-trust-gate",
                    [
                        py,
                        "scripts/kernel_live_agent_battery.py",
                        "--pillar",
                        "user_trust,chat_core,sidebar,preferences",
                        "--gate",
                        "--delay",
                        "5",
                    ],
                ),
                (
                    "live-worker-gate",
                    [py, "scripts/kernel_live_worker_battery.py", "--cleanup", "--gate", "--delay", "10"],
                ),
                (
                    "live-delivery-gate",
                    [py, "scripts/kernel_live_delivery_battery.py", "--cleanup", "--gate", "--delay", "10"],
                ),
            ]
            for label, cmd in live_cmds:
                steps.append(_run(cmd, label=label))

    if not args.skip_swift:
        steps.append(_run(["make", "-s", "test-swift"], label="test-swift"))

    failed = [s for s in steps if not s.get("ok")]
    print("\n" + "=" * 60)
    if failed:
        print(f"RELEASE GATE: FAIL ({len(failed)} step(s))")
        for s in failed:
            print(f"  - {s['label']}")
    else:
        print("RELEASE GATE: PASS")
    print("=" * 60)

    report = {
        "ok": not failed,
        "failed_steps": [s["label"] for s in failed],
        "steps": steps,
    }
    if args.json:
        with open(args.json, "w", encoding="utf-8") as f:
            json.dump(report, f, indent=2)
        print(f"Wrote {args.json}")

    return 0 if not failed else 1


if __name__ == "__main__":
    raise SystemExit(main())
