"""Three-way wire-parity check.

For each backend (FastAPI, Rust axum):
  1. Boot the fixture server on a free port.
  2. Poll /api/mizan/session/ until the server responds.
  3. Run the Rust `drive_kernel` binary (raw kernel calls) against it.
  4. Run the Rust `drive_emitted` binary (typed codegen functions) against it.
  5. Tear the server down.

Any non-zero driver exit propagates as the script's exit code. Adding the
Rust backend here proves that mizan-axum honors the same wire contract as
mizan-fastapi — same JSON shapes, same invalidate/merge semantics — beyond
the static IR equivalence the codegen-parity test gates.

Readiness probe is `/api/mizan/session/` (Mizan-protocol-shaped) rather
than `/openapi.json` (FastAPI-feature-shaped) so the harness reads the
same surface across backends.
"""

from __future__ import annotations

import os
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[2]
AFI_DIR = REPO_ROOT / "tests" / "afi"
RUST_DIR = REPO_ROOT / "tests" / "rust"
RUST_APP_DIR = AFI_DIR / "rust_app"
BOOT_TIMEOUT_S = 15.0
POLL_INTERVAL_S = 0.25


def pick_free_port() -> int:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


def wait_for_server(port: int, timeout_s: float, label: str) -> bool:
    deadline = time.monotonic() + timeout_s
    url = f"http://127.0.0.1:{port}/api/mizan/session/"
    while time.monotonic() < deadline:
        try:
            with urllib.request.urlopen(url, timeout=1.0) as resp:
                if resp.status == 200:
                    return True
        except (urllib.error.URLError, ConnectionError, OSError) as e:
            sys.stderr.write(f"[wire_parity:{label}] waiting: {type(e).__name__}\n")
        time.sleep(POLL_INTERVAL_S)
    return False


def run_driver(name: str, base_url: str, label: str) -> int:
    sys.stdout.write(f"\n=== {label} :: {name} ===\n")
    sys.stdout.flush()
    return subprocess.run(
        ["cargo", "run", "--quiet", "--bin", name, "--", base_url],
        cwd=RUST_DIR,
    ).returncode


def boot_fastapi(port: int) -> subprocess.Popen:
    return subprocess.Popen(
        [
            "uv", "run", "uvicorn", "fastapi_app:make_app",
            "--factory", "--port", str(port), "--log-level", "warning",
        ],
        cwd=AFI_DIR,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.PIPE,
    )


def boot_rust(port: int) -> subprocess.Popen:
    return subprocess.Popen(
        [
            "cargo", "run", "--quiet", "--release",
            "--manifest-path", str(RUST_APP_DIR / "Cargo.toml"),
            "--bin", "server",
        ],
        env={**os.environ, "PORT": str(port)},
        stdout=subprocess.DEVNULL,
        stderr=subprocess.PIPE,
    )


def run_against(label: str, server: subprocess.Popen, port: int) -> int:
    failures = 0
    try:
        if not wait_for_server(port, BOOT_TIMEOUT_S, label):
            sys.stderr.write(f"[wire_parity:{label}] server boot timed out after {BOOT_TIMEOUT_S}s\n")
            if server.stderr:
                tail = server.stderr.read(4096)
                if tail:
                    sys.stderr.write(tail.decode("utf-8", errors="replace"))
            return 1
        base_url = f"http://127.0.0.1:{port}/api/mizan"
        for driver in ("drive_kernel", "drive_emitted"):
            rc = run_driver(driver, base_url, label)
            if rc != 0:
                sys.stderr.write(f"[wire_parity:{label}] {driver} exited {rc}\n")
                failures += 1
    finally:
        server.terminate()
        try:
            server.wait(timeout=3)
        except subprocess.TimeoutExpired:
            server.kill()
            server.wait()
    return failures


def main() -> int:
    total_failures = 0

    fastapi_port = pick_free_port()
    sys.stdout.write(f"[wire_parity] booting fastapi on port {fastapi_port}\n")
    total_failures += run_against("fastapi", boot_fastapi(fastapi_port), fastapi_port)

    rust_port = pick_free_port()
    sys.stdout.write(f"[wire_parity] booting rust on port {rust_port}\n")
    total_failures += run_against("rust", boot_rust(rust_port), rust_port)

    return 0 if total_failures == 0 else 1


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