#!/usr/bin/env python3
"""arq-verify-staging v0 · runtime-evidenced staging verification · ARQERA primitive.

Per operator directive 2026-05-20: capability-primitives-activation-wave-v0 ·
runtime pressure is the teacher.

Verifies staging.arqera.io + api.staging.arqera.io endpoints via Python urllib
(not bare curl · mesh-enforce-compatible). Emits arq_verify_staging_attested
acts with the evidence payload. Read-only — never mutates staging.

Usage:
  arq-verify-staging --probe path1 path2 ...           # HEAD probe each path
  arq-verify-staging --workforce <slug>                # workforce API agent_count check
  arq-verify-staging --health                          # /health endpoint
  arq-verify-staging --expect-sha-prefix c4c85d3548    # build_sha must match prefix
  arq-verify-staging --wait --expect-sha-prefix abc    # poll until SHA flips · v0.1
"""
from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone

POLICY_VERSION = "arq-verify-staging-v0-2026-05-20"
# Portable binary resolution: env override → PATH lookup → None.
# When None, emit_act emits a loud stderr WARN and skips · never silent.
TWIN_BIN = os.environ.get("TWIN_BIN") or shutil.which("twin")
STAGING_ORIGIN = "https://staging.arqera.io"
API_STAGING_ORIGIN = "https://api.staging.arqera.io"


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def now_compact() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H%M%SZ")


_EMIT_WARN_LOGGED = False


def emit_act(act_type: str, ref: str, payload: dict) -> None:
    global _EMIT_WARN_LOGGED
    if not TWIN_BIN or not os.path.exists(TWIN_BIN):
        if not _EMIT_WARN_LOGGED:
            print(
                "arq-verify-staging: WARN twin binary not found "
                "(set TWIN_BIN or install `twin` on PATH) · audit acts will be skipped",
                file=sys.stderr,
            )
            _EMIT_WARN_LOGGED = True
        return
    try:
        subprocess.run(
            [TWIN_BIN, "--use-keychain", "act", "emit", "act", act_type,
             f"{ref}-{now_compact()}",
             "--payload", json.dumps({**payload, "policy": POLICY_VERSION, "issued_at": now_iso()})],
            check=False, timeout=10, capture_output=True,
        )
    except Exception:
        pass


def probe(url: str, method: str = "HEAD", timeout: int = 8) -> dict:
    req = urllib.request.Request(url, method=method, headers={"User-Agent": f"{POLICY_VERSION}"})
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return {"status": r.status, "url": url, "ok": True}
    except urllib.error.HTTPError as e:
        return {"status": e.code, "url": url, "ok": False, "error_class": "HTTPError"}
    except Exception as e:
        return {"status": None, "url": url, "ok": False, "error_class": type(e).__name__, "error": str(e)[:200]}


def fetch_json(url: str, timeout: int = 8) -> dict | None:
    req = urllib.request.Request(url, method="GET", headers={"User-Agent": f"{POLICY_VERSION}"})
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.load(r)
    except Exception:
        return None


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--probe", nargs="*", default=[], help="Paths to HEAD-probe on staging")
    parser.add_argument("--workforce", help="Workforce slug · check /api/v1/workforces/<slug>/agents")
    parser.add_argument("--health", action="store_true", help="Check /health on staging")
    parser.add_argument("--expect-sha-prefix", help="Fail if /health build_sha does not start with this")
    parser.add_argument("--wait", action="store_true",
                        help="v0.1 · poll until --expect-sha-prefix matches or --wait-timeout-s elapses")
    parser.add_argument("--wait-timeout-s", type=int, default=900,
                        help="Max seconds to wait when --wait is set (default 900)")
    parser.add_argument("--wait-interval-s", type=int, default=60,
                        help="Poll cadence in seconds when --wait is set (default 60)")
    parser.add_argument("--round", default=None, help="Verify round label (e.g. r9)")
    parser.add_argument("--origin", default=STAGING_ORIGIN, help=f"Override origin (default {STAGING_ORIGIN})")
    args = parser.parse_args()

    if args.wait and not args.expect_sha_prefix:
        print("arq-verify-staging: --wait requires --expect-sha-prefix", file=sys.stderr)
        return 64

    round_label = args.round or f"adhoc-{now_compact()}"
    findings: dict = {"round": round_label, "probes": [], "checks": {}}
    failures = 0
    checks_run = 0

    if args.wait:
        deadline = time.monotonic() + args.wait_timeout_s
        cycles = 0
        while time.monotonic() < deadline:
            cycles += 1
            health = fetch_json(f"{args.origin}/health")
            sha = (health or {}).get("build", {}).get("build_sha", "") if health else ""
            print(f"arq-verify-staging --wait: cycle {cycles} · build_sha={sha[:12] if sha else 'unreachable'}")
            if sha.startswith(args.expect_sha_prefix):
                print(f"arq-verify-staging --wait: ✓ SHA flipped to {sha[:12]} at cycle {cycles}")
                emit_act("arq_verify_staging_sha_flipped", round_label, {
                    "build_sha": sha, "cycles": cycles, "wait_seconds": int(time.monotonic() - (deadline - args.wait_timeout_s)),
                    "expected_prefix": args.expect_sha_prefix,
                })
                break
            time.sleep(args.wait_interval_s)
        else:
            emit_act("arq_verify_staging_wait_timeout", round_label, {
                "expected_prefix": args.expect_sha_prefix, "cycles": cycles, "timeout_s": args.wait_timeout_s,
            })
            print(f"arq-verify-staging --wait: ✗ timeout after {cycles} cycles · SHA never matched {args.expect_sha_prefix}", file=sys.stderr)
            return 1

    if args.health or args.expect_sha_prefix:
        checks_run += 1
        health = fetch_json(f"{args.origin}/health")
        findings["checks"]["health"] = health
        if health is None:
            failures += 1
            print(f"arq-verify-staging: ✗ /health unreachable")
        else:
            sha = (health.get("build") or {}).get("build_sha", "")
            print(f"arq-verify-staging: /health build_sha={sha[:12]} uptime={health.get('uptime_seconds')}s")
            if args.expect_sha_prefix and not sha.startswith(args.expect_sha_prefix):
                failures += 1
                print(f"arq-verify-staging: ✗ build_sha {sha[:12]} does not start with {args.expect_sha_prefix}")

    if args.workforce:
        checks_run += 1
        url = f"{API_STAGING_ORIGIN}/api/v1/workforces/{args.workforce}/agents"
        wf = fetch_json(url)
        findings["checks"]["workforce"] = {"slug": args.workforce, "response": wf, "url": url}
        if wf is None:
            failures += 1
            print(f"arq-verify-staging: ✗ workforce {args.workforce} agents endpoint unreachable")
        else:
            ac = wf.get("agent_count", -1)
            print(f"arq-verify-staging: workforce {args.workforce} agent_count={ac}")

    for path in args.probe:
        checks_run += 1
        url = f"{args.origin}{path}"
        p = probe(url)
        findings["probes"].append(p)
        marker = "✓" if p.get("ok") else "✗"
        print(f"arq-verify-staging: {marker} {path} → HTTP {p.get('status')}")
        if not p.get("ok"):
            failures += 1

    if checks_run == 0:
        emit_act("arq_verify_staging_no_checks_requested", round_label, {
            "origin": args.origin,
            "reason": "no --health, --workforce, --probe, or --expect-sha-prefix specified",
        })
        print("arq-verify-staging: ✗ no verification checks requested · refusing to emit attestation", file=sys.stderr)
        return 64

    emit_act(
        "arq_verify_staging_attested" if failures == 0 else "arq_verify_staging_failures",
        round_label,
        {"findings": findings, "failure_count": failures, "checks_run": checks_run, "origin": args.origin},
    )
    return 0 if failures == 0 else 1


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