"""A small fan-out/reduce/verify workflow for auditing route authentication.

Requirements:
  - Claude Code is installed and authenticated.
  - Run from the repository root.

Example:
  python3 claude_route_audit.py 'src/routes/**/*.ts'
"""

from __future__ import annotations

import glob
import json
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from pathlib import Path
from typing import Any


MAX_FILES = 20
MAX_WORKERS = 6


@dataclass(frozen=True)
class Finding:
    file: str
    status: str
    evidence: tuple[str, ...]
    risk: str


def ask_claude(prompt: str, *, max_turns: int = 4) -> dict[str, Any]:
    """Start a fresh Claude Code process so workers do not share context."""
    completed = subprocess.run(
        [
            "claude",
            "-p",
            prompt,
            "--output-format",
            "json",
            "--max-turns",
            str(max_turns),
        ],
        check=True,
        capture_output=True,
        text=True,
    )
    envelope = json.loads(completed.stdout)
    result = envelope.get("result")
    if not isinstance(result, str):
        raise ValueError("Claude CLI did not return a string result")
    parsed = json.loads(result)
    if not isinstance(parsed, dict):
        raise ValueError("Claude result must be a JSON object")
    return parsed


def audit_one(path: Path) -> Finding:
    prompt = f"""
You are a route-auth auditor. Read only {path} and the auth helpers it imports.
Decide whether every externally reachable handler has an authentication or
explicit public-route policy. Return JSON only:
{{"status":"protected|missing|unknown","evidence":["file:line ..."],"risk":"..."}}
Do not edit files. Mark unknown when evidence is insufficient.
""".strip()
    data = ask_claude(prompt)
    return Finding(
        file=str(path),
        status=str(data["status"]),
        evidence=tuple(str(item) for item in data.get("evidence", [])),
        risk=str(data.get("risk", "")),
    )


def verify_one(finding: Finding) -> bool:
    """Use a new context, then combine its judgment with deterministic checks."""
    if not Path(finding.file).is_file() or not finding.evidence:
        return False
    prompt = f"""
Act as an independent verifier. Re-open {finding.file}. Check this candidate:
{json.dumps(finding.__dict__, ensure_ascii=False)}
Return JSON only: {{"pass":true|false,"reason":"..."}}.
Reject claims whose cited lines do not exist or do not support the status.
Do not trust the candidate merely because it is detailed.
""".strip()
    return ask_claude(prompt, max_turns=3).get("pass") is True


def main(pattern: str) -> int:
    files = [Path(item) for item in sorted(glob.glob(pattern, recursive=True))]
    files = [item for item in files if item.is_file()][:MAX_FILES]
    if not files:
        raise SystemExit(f"no files matched: {pattern}")

    findings: list[Finding] = []
    errors: list[str] = []
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
        future_to_file = {pool.submit(audit_one, path): path for path in files}
        for future in as_completed(future_to_file):
            path = future_to_file[future]
            try:
                findings.append(future.result())
            except Exception as exc:  # The failure is recorded, not silently dropped.
                errors.append(f"{path}: {exc}")

    # Barrier accounting: an incomplete fan-out must not produce a green report.
    if len(findings) + len(errors) != len(files) or errors:
        print(json.dumps({"status": "incomplete", "errors": errors}, ensure_ascii=False, indent=2))
        return 2

    candidates = [item for item in findings if item.status in {"missing", "unknown"}]
    verified = [item for item in candidates if verify_one(item)]
    report = {
        "status": "complete",
        "scanned": len(files),
        "candidates": len(candidates),
        "verified": [item.__dict__ for item in verified],
    }
    print(json.dumps(report, ensure_ascii=False, indent=2))
    return 1 if any(item.status == "missing" for item in verified) else 0


if __name__ == "__main__":
    if len(sys.argv) != 2:
        raise SystemExit("usage: python3 claude_route_audit.py 'src/routes/**/*.ts'")
    raise SystemExit(main(sys.argv[1]))
