#!/usr/bin/env python3
"""SSOT spec auditor — verdict recorder.

LLM judgment now lives in the /spec-review skill (in-session sub-agent
dispatch via the Agent tool), because JT runs OAuth-only and the SDK path
hits 401/429 against the Anthropic billing surface. This script handles
only the deterministic file I/O: append to auditor_log.jsonl, write
BUILD_SPEC.violation.json on FAIL.

Usage (from the /spec-review skill, after sub-agent returns a verdict):
    python recoil/architecture/tools/audit_spec_against_ssot.py record \\
        --spec BUILD_SPEC.md \\
        --manifest recoil/architecture/ssot_manifest.yaml \\
        --verdict PASS \\
        --reason "All capability references resolve to canonical paths."

    python recoil/architecture/tools/audit_spec_against_ssot.py record \\
        --spec BUILD_SPEC.md \\
        --manifest recoil/architecture/ssot_manifest.yaml \\
        --verdict FAIL \\
        --reason "Phase 4 routes around pipeline.core.dispatch via direct StepRunner call."

    # Override/bypass (logs the reason):
    python recoil/architecture/tools/audit_spec_against_ssot.py \\
        --spec BUILD_SPEC.md \\
        --manifest recoil/architecture/ssot_manifest.yaml \\
        --bypass-ssot-gate-with-explanation "Tartarus EP001 crunch — critical hotfix"

    # Correct a past verdict:
    python recoil/architecture/tools/audit_spec_against_ssot.py \\
        --correct <verdict-id> PASS --reason "False positive — phase uses modality registry correctly"

Exit codes:
    0 = PASS / BYPASS / correction recorded
    1 = FAIL (BUILD_SPEC.violation.json written)
    2 = invalid arguments / missing inputs
"""
from __future__ import annotations

import argparse
import json
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path

AUDIT_LOG = Path("recoil/architecture/auditor_log.jsonl")
CORRECTIONS_LOG = Path("recoil/architecture/auditor_corrections.jsonl")
VIOLATION_FILE = Path("BUILD_SPEC.violation.json")


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


def _log_verdict(
    spec_path: str,
    manifest_path: str,
    verdict: str,
    reason: str,
    build_id: str = "",
    bypassed: bool = False,
    bypass_explanation: str | None = None,
) -> str:
    entry = {
        "id": str(uuid.uuid4()),
        "timestamp": _now_iso(),
        "spec_path": spec_path,
        "manifest_path": manifest_path,
        "verdict": verdict,
        "reason": reason,
        "build_id": build_id,
        "bypassed": bypassed,
        "bypass_explanation": bypass_explanation,
        "judgment_source": "in_session_subagent" if not bypassed else "manual_bypass",
    }
    AUDIT_LOG.parent.mkdir(parents=True, exist_ok=True)
    with open(AUDIT_LOG, "a") as f:
        f.write(json.dumps(entry) + "\n")
    return entry["id"]


def _write_violation_json(reason: str) -> None:
    data = {
        "verdict": "FAIL",
        "violations": [{
            "phase": "unknown",
            "reason": reason,
            "suggested_fix": "Rewrite the offending phase to route through the canonical path listed in ssot_manifest.yaml",
        }],
        "auditor_version": "2.0",
        "timestamp": _now_iso(),
    }
    VIOLATION_FILE.write_text(json.dumps(data, indent=2))
    print(f"Violation written to: {VIOLATION_FILE}", file=sys.stderr)


def _clear_violation_json() -> None:
    if VIOLATION_FILE.exists():
        VIOLATION_FILE.unlink()


def _do_correct(verdict_id: str, correct_verdict: str, reason: str) -> None:
    entry = {
        "id": str(uuid.uuid4()),
        "timestamp": _now_iso(),
        "original_verdict_id": verdict_id,
        "correct_verdict": correct_verdict,
        "reason": reason,
    }
    CORRECTIONS_LOG.parent.mkdir(parents=True, exist_ok=True)
    with open(CORRECTIONS_LOG, "a") as f:
        f.write(json.dumps(entry) + "\n")
    print(f"Correction logged: {verdict_id} → {correct_verdict}")


def _check_inputs(spec_path: Path, manifest_path: Path) -> int:
    if not spec_path.exists():
        print(f"ERROR: spec not found: {spec_path}", file=sys.stderr)
        return 2
    if not manifest_path.exists():
        print(f"ERROR: manifest not found: {manifest_path}", file=sys.stderr)
        return 2
    return 0


def main() -> int:
    p = argparse.ArgumentParser(description="SSOT spec auditor — verdict recorder")
    p.add_argument("--spec", help="Path to BUILD_SPEC.md")
    p.add_argument("--manifest", default="recoil/architecture/ssot_manifest.yaml")
    p.add_argument("--bypass-ssot-gate-with-explanation", metavar="REASON",
                   help="Override FAIL verdict; logs explanation")
    p.add_argument("--correct", nargs=3, metavar=("VERDICT_ID", "CORRECT_VERDICT", "REASON"),
                   help="Post-hoc correction: --correct <id> <PASS|FAIL> <reason>")
    p.add_argument("--build-id", default="")

    sub = p.add_subparsers(dest="cmd")
    rec = sub.add_parser("record", help="Record a verdict produced by the in-session sub-agent.")
    rec.add_argument("--spec", required=True)
    rec.add_argument("--manifest", default="recoil/architecture/ssot_manifest.yaml")
    rec.add_argument("--verdict", required=True, choices=["PASS", "FAIL"])
    rec.add_argument("--reason", required=True)
    rec.add_argument("--build-id", default="")

    args = p.parse_args()

    # Correction mode (no spec required)
    if args.correct:
        verdict_id, correct_verdict, reason = args.correct
        _do_correct(verdict_id, correct_verdict, reason)
        return 0

    # Record mode — verdict provided externally (by the skill's sub-agent dispatch)
    if args.cmd == "record":
        spec_path = Path(args.spec)
        manifest_path = Path(args.manifest)
        rc = _check_inputs(spec_path, manifest_path)
        if rc != 0:
            return rc

        vid = _log_verdict(
            str(spec_path), str(manifest_path),
            verdict=args.verdict,
            reason=args.reason,
            build_id=args.build_id,
        )
        print(f"Verdict: {args.verdict} (id={vid})")
        print(f"Reason:  {args.reason}")

        if args.verdict == "FAIL":
            _write_violation_json(args.reason)
            return 1

        _clear_violation_json()
        return 0

    # Bypass mode (no sub-agent dispatch needed)
    if args.bypass_ssot_gate_with_explanation:
        if not args.spec:
            p.error("--spec is required for bypass")
        spec_path = Path(args.spec)
        manifest_path = Path(args.manifest)
        rc = _check_inputs(spec_path, manifest_path)
        if rc != 0:
            return rc

        explanation = args.bypass_ssot_gate_with_explanation
        vid = _log_verdict(
            str(spec_path), str(manifest_path),
            verdict="BYPASS",
            reason=f"Manual bypass: {explanation}",
            build_id=args.build_id,
            bypassed=True,
            bypass_explanation=explanation,
        )
        print(f"BYPASS logged (id={vid}): {explanation}")
        _clear_violation_json()
        return 0

    # No subcommand and no bypass — the legacy API-driven audit path is gone.
    # Print the new contract and exit non-zero so any stale caller fails loud.
    print(
        "ERROR: audit_spec_against_ssot.py no longer performs the LLM call itself.\n"
        "       The /spec-review skill dispatches an in-session sub-agent for judgment,\n"
        "       then calls this script with the 'record' subcommand.\n\n"
        "       If you are invoking this manually, run /spec-review on the spec instead.\n"
        "       To bypass: --bypass-ssot-gate-with-explanation \"<reason>\".",
        file=sys.stderr,
    )
    return 2


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