#!/usr/bin/env python3
"""Smoke-test the route external proof validator CLI."""
from __future__ import annotations

import json
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
VALIDATOR = ROOT / "tools" / "validate_route_promotion_external_proof.py"
REFRESH = ROOT / "tools" / "refresh_savedata_route_proof.py"
TEMPLATE = ROOT / "out" / "route_promotion_external_proof_template.json"
SYNTHETIC_SAVEDAT = ROOT / "out" / "synthetic_savedat_selector_2_0.dat"


def run_validator(*args: str) -> tuple[int, dict[str, Any]]:
    proc = subprocess.run(
        [sys.executable, str(VALIDATOR), *args, "--json"],
        cwd=ROOT,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    try:
        data = json.loads(proc.stdout)
    except json.JSONDecodeError as exc:
        raise AssertionError(
            f"validator did not write JSON for args {args!r}; "
            f"code={proc.returncode} stdout={proc.stdout!r} stderr={proc.stderr!r}"
        ) from exc
    return proc.returncode, data


def write_json(path: Path, data: Any) -> None:
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def check_template_require_accepted_fails() -> None:
    code, data = run_validator(str(TEMPLATE), "--require-accepted")
    if (
        code == 0
        or data.get("inputMode") != "template-only"
        or data.get("recordCount") != 0
        or data.get("acceptedRecordCount") != 0
        or data.get("proofFound") is not False
        or data.get("promotionStatus") != "blocked"
    ):
        raise AssertionError(f"template-only --require-accepted should fail, got code={code} data={data}")


def check_runtime_trace_fixture_accepts(tmp: Path) -> None:
    record = {
        "inputId": "normal-route-runtime-trace",
        "traceTool": "validator self-test fixture",
        "normalRoutePath": True,
        "diagnosticRun": False,
        "selectedPointerGlobalVaHex": "0x0059de30",
        "selectedPointerValueHex": "0x00540714",
        "selectedRootExecutionRefFound": True,
        "observedTracePoints": [
            {"addressHex": "0x00542b0c", "event": "frontier reader fixture"},
        ],
    }
    path = tmp / "accepted_trace_fixture.json"
    write_json(path, record)
    code, data = run_validator(str(path), "--require-accepted")
    checks = {
        check.get("id"): check.get("passed")
        for result in data.get("recordResults") or []
        for check in result.get("checks") or []
    }
    if (
        code != 0
        or data.get("inputMode") != "single-record"
        or data.get("acceptedRecordCount") != 1
        or data.get("proofFound") is not True
        or data.get("promotionStatus") != "external-proof-ready-for-refresh"
        or data.get("acceptedInputIds") != ["normal-route-runtime-trace"]
        or checks.get("accepted-trace-point") is not True
    ):
        raise AssertionError(f"runtime trace fixture should be accepted, got code={code} data={data}")


def accepted_runtime_trace_record() -> dict[str, Any]:
    return {
        "inputId": "normal-route-runtime-trace",
        "traceTool": "validator wrapper fixture",
        "normalRoutePath": True,
        "diagnosticRun": False,
        "selectedPointerGlobalVaHex": "0x0059de30",
        "selectedPointerValueHex": "0x00540714",
        "equivalentSelectedRootProofFound": True,
        "observedTracePoints": [
            {"addressHex": "0x0040c675", "event": "opcode24 mode1 read fixture"},
        ],
    }


def check_record_wrapper_accepts_one_record(tmp: Path) -> None:
    payload = {
        "submittedRecords": [
            {"inputId": "unknown-proof", "notes": "rejected wrapper fixture"},
            accepted_runtime_trace_record(),
        ],
    }
    path = tmp / "submitted_records_wrapper_fixture.json"
    write_json(path, payload)
    code, data = run_validator(str(path), "--require-accepted")
    statuses = [result.get("status") for result in data.get("recordResults") or []]
    if (
        code != 0
        or data.get("inputMode") != "submittedRecords"
        or data.get("recordCount") != 2
        or data.get("acceptedRecordCount") != 1
        or data.get("proofFound") is not True
        or data.get("acceptedInputIds") != ["normal-route-runtime-trace"]
        or statuses != ["rejected", "accepted"]
    ):
        raise AssertionError(f"submittedRecords wrapper should accept one record, got code={code} data={data}")


def check_refresh_wrapper_forwards_proof_json(tmp: Path) -> None:
    path = tmp / "accepted_trace_for_refresh_fixture.json"
    write_json(path, accepted_runtime_trace_record())
    proc = subprocess.run(
        [
            sys.executable,
            str(REFRESH),
            "--proof-json",
            str(path),
            "--require-accepted-proof",
            "--dry-run",
            "--no-verify",
        ],
        cwd=ROOT,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
    )
    expected = f"+ python3 tools/validate_route_promotion_external_proof.py {path} --require-accepted"
    if proc.returncode != 0 or expected not in proc.stdout:
        raise AssertionError(
            "refresh wrapper should forward --proof-json/--require-accepted-proof to validator; "
            f"code={proc.returncode} expected={expected!r} stdout={proc.stdout!r} stderr={proc.stderr!r}"
        )


def check_strict_hotspot_fixture_accepts(tmp: Path) -> None:
    record = {
        "inputId": "strict-source-hotspot",
        "sourceMap": "map1_01a",
        "targetMap": "map2_02d",
        "candidateSide": "right",
        "sourceTile": {"x": 34, "y": 19},
        "targetSpawn": {"x": 1, "y": 14},
        "strictSourceHotspotProofFound": True,
        "tileHotspotConfirmed": True,
        "selectorOnlySceneListAdjacency": False,
        "supportingArtifact": "validator accepted strict hotspot fixture",
    }
    path = tmp / "accepted_strict_hotspot_fixture.json"
    write_json(path, record)
    code, data = run_validator(str(path), "--require-accepted")
    checks = {
        check.get("id"): check.get("passed")
        for result in data.get("recordResults") or []
        for check in result.get("checks") or []
    }
    if (
        code != 0
        or data.get("inputMode") != "single-record"
        or data.get("acceptedRecordCount") != 1
        or data.get("proofFound") is not True
        or data.get("acceptedInputIds") != ["strict-source-hotspot"]
        or checks.get("strict-proof") is not True
        or checks.get("tile-proof") is not True
        or checks.get("not-selector-only") is not True
    ):
        raise AssertionError(f"strict hotspot fixture should be accepted, got code={code} data={data}")


def check_strict_hotspot_selector_only_rejects(tmp: Path) -> None:
    record = {
        "inputId": "strict-source-hotspot",
        "sourceMap": "map1_01a",
        "targetMap": "map2_02d",
        "candidateSide": "top",
        "sourceTile": {"x": 18, "y": 0},
        "targetSpawn": {"x": 16, "y": 47},
        "strictSourceHotspotProofFound": True,
        "tileHotspotConfirmed": True,
        "selectorOnlySceneListAdjacency": True,
        "supportingArtifact": "validator selector-only rejection fixture",
    }
    path = tmp / "strict_selector_only_fixture.json"
    write_json(path, record)
    code, data = run_validator(str(path), "--require-accepted")
    checks = {
        check.get("id"): check.get("passed")
        for result in data.get("recordResults") or []
        for check in result.get("checks") or []
    }
    if (
        code == 0
        or data.get("acceptedRecordCount") != 0
        or data.get("proofFound") is not False
        or checks.get("not-selector-only") is not False
    ):
        raise AssertionError(f"selector-only strict hotspot fixture should be rejected, got code={code} data={data}")


def check_synthetic_savedata_rejects(tmp: Path) -> None:
    if not SYNTHETIC_SAVEDAT.exists():
        raise AssertionError(f"missing synthetic savedata fixture: {SYNTHETIC_SAVEDAT}")
    record = {
        "inputId": "real-selector-2-0-save",
        "path": str(SYNTHETIC_SAVEDAT.relative_to(ROOT)),
        "capturedFromGameplay": True,
        "selector": "2:0",
        "selectedPointerHex": "0x00540714",
        "fieldMaps": ["map1_01a", "map2_02d"],
        "syntheticDiagnostic": False,
        "notes": "validator synthetic savedata rejection fixture",
    }
    path = tmp / "synthetic_savedata_claim_fixture.json"
    write_json(path, record)
    code, data = run_validator(str(path), "--require-accepted")
    checks = {
        check.get("id"): check.get("passed")
        for result in data.get("recordResults") or []
        for check in result.get("checks") or []
    }
    actual = ((data.get("recordResults") or [{}])[0].get("actualScanRow") or {})
    if (
        code == 0
        or data.get("acceptedRecordCount") != 0
        or data.get("proofFound") is not False
        or checks.get("actual-synthetic-false") is not False
        or actual.get("syntheticDiagnostic") is not True
    ):
        raise AssertionError(f"synthetic savedata claim should be rejected, got code={code} data={data}")


def main() -> None:
    check_template_require_accepted_fails()
    with tempfile.TemporaryDirectory(prefix="hwanse-proof-validator-") as tmp_name:
        tmp = Path(tmp_name)
        check_runtime_trace_fixture_accepts(tmp)
        check_record_wrapper_accepts_one_record(tmp)
        check_refresh_wrapper_forwards_proof_json(tmp)
        check_strict_hotspot_fixture_accepts(tmp)
        check_strict_hotspot_selector_only_rejects(tmp)
        check_synthetic_savedata_rejects(tmp)
    print("ok route promotion external proof validator")


if __name__ == "__main__":
    main()
