"""Official isolated fixture runner for the packaged R2 shadow candidate."""

from __future__ import annotations

import argparse
import copy
import hashlib
import importlib.util
import json
import math
import sys
from pathlib import Path
from typing import Any, Mapping

EXPECTED_POLICY_DIGEST = "505a0cdd72feb71dab16aecadf9373c81994e14a28cefa7b6413882746a9f3d5"
EXPECTED_CLASSIFIER_VERSION = "2.0.0"
EXPECTED_COUNTS = {"migrated": 12, "adversarial": 30, "residual": 12, "total": 54}


def canonical_bytes(value: Any) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8")


def sha256_file(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def load_json(path: Path) -> Any:
    return json.loads(path.read_text(encoding="utf-8"))


def load_candidate(path: Path) -> Any:
    spec = importlib.util.spec_from_file_location("isolated_validation_tiering_candidate", path)
    if spec is None or spec.loader is None:
        raise RuntimeError("candidate_load_spec_failed")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def base_fingerprint(policy_digest: str) -> dict[str, Any]:
    return {
        "product_source": "p", "test_source": "t", "test_collection": "c", "runner": "r",
        "orchestrator": "o", "authority": "a", "python_runtime": "3.x", "platform": "windows",
        "docker_image_digest": "not_applicable", "server_os": "windows", "config": "cfg",
        "assertion_policy": "strict", "timeout_policy": "unchanged", "extraction": "e",
        "candidate": "candidate", "order": "default", "seed": 1, "temperature": "cold",
        "resource_profile": "LIGHT", "predecessor": "root", "cleanup_state": "PASS",
        "scope_identity": "scope", "policy_digest": policy_digest,
    }


def complete_receipt(candidate: Any, policy: Any, current: Mapping[str, Any]) -> dict[str, Any]:
    fp = candidate.fingerprint_result(current, policy)
    return {
        "status": "PASS", "immutable": True, "complete": True, "fingerprint": fp.get("fingerprint"),
        "policy_digest": current["policy_digest"], "scope_identity": current["scope_identity"],
        "predecessor": current["predecessor"], "cleanup_state": current["cleanup_state"],
    }


def package_entries(count: int) -> list[dict[str, Any]]:
    return [{"path": f"fixtures/generated_{index}.json", "logical_size": 1, "compressed_size": 1, "magic": "json"} for index in range(count)]


def run_operation(case: Mapping[str, Any], candidate: Any, policy: Any) -> dict[str, Any]:
    op = case["operation"]
    if op == "classify":
        return candidate.classify_change(case["input"], policy)
    if op == "plan":
        return candidate.build_plan(case["input"], policy)
    if op == "invalidate":
        return candidate.invalidation_scope(case["event"], case.get("receipt", {}), policy)
    if op == "reuse":
        current = base_fingerprint(EXPECTED_POLICY_DIGEST)
        receipt = complete_receipt(candidate, policy, current)
        materialize = case.get("materialize", {})
        if materialize.get("mode") == "set":
            current[materialize["field"]] = {"non-json"}
        elif materialize.get("mode") == "nonfinite_all":
            results = []
            for value in (math.nan, math.inf, -math.inf):
                variant = dict(current)
                variant[materialize["field"]] = value
                results.append(candidate.can_reuse(receipt, variant, policy))
            return {"reuse": any(item.get("reuse") is True for item in results), "variant_count": len(results), "all_rejected": all(item.get("reuse") is False for item in results)}
        elif materialize.get("mode") == "missing":
            current.pop(materialize["field"], None)
        current.update(case.get("current_override", {}))
        receipt.update(case.get("receipt_override", {}))
        return candidate.can_reuse(receipt, current, policy)
    if op == "package":
        entries = copy.deepcopy(case.get("entries", []))
        if "entry_count" in case.get("materialize", {}):
            entries = package_entries(case["materialize"]["entry_count"])
        candidate_policy = copy.deepcopy(policy)
        if "policy_override" in case:
            for path, value in case["policy_override"].items():
                section, key = path.split(".", 1)
                candidate_policy[section][key] = value
        supplied = case.get("supplied_policy_digest", EXPECTED_POLICY_DIGEST)
        return candidate.package_guard(entries, candidate_policy, supplied)
    if op == "schedule":
        return candidate.scheduler_decision(case["job"], case.get("active", []), case["available_capacity"], policy)
    if op == "eta":
        return candidate.progress_eta(case.get("rows", []), case["tier"], case["resource_class"], case["kind"], case["remaining_weight"], policy, case.get("run_state", "RUNNING"))
    if op == "docker_readiness":
        return candidate.docker_readiness(case["observation"], case["linux_required"], policy)
    if op == "policy_equivalence":
        check = candidate.validate_policy(policy, EXPECTED_POLICY_DIGEST)
        return {
            "valid": check["valid"],
            "policy_digest": candidate.canonical_policy_digest(policy),
            "classifier_version": policy["classifier_version"],
            "t4_matrix_preserved": policy["t4_matrix"] == ["full_authority_matrix", "full_platform_matrix", "all_order_seed_temperature_axes", "final_package_gates"],
        }
    raise ValueError("unknown_fixture_operation:" + str(op))


def expected_matches(expected: Mapping[str, Any], actual: Mapping[str, Any], exception_count: int) -> tuple[bool, list[str]]:
    failures: list[str] = []
    for key, value in expected.items():
        if key == "exception_count":
            observed = exception_count
        elif key == "reason":
            reasons = actual.get("reasons", [])
            blocked_reasons = [item.get("reason") for item in actual.get("blocked", []) if isinstance(item, Mapping)]
            observed = actual.get("reason")
            if observed != value and value not in reasons and value not in blocked_reasons:
                failures.append(f"reason expected={value!r} actual={actual!r}")
            continue
        elif key == "blocked_count_min":
            if len(actual.get("blocked", [])) < value:
                failures.append(f"blocked_count_min expected>={value} actual={len(actual.get('blocked', []))}")
            continue
        elif key == "range":
            observed = actual.get("eta_seconds_range")
        else:
            observed = actual.get(key)
        if observed != value:
            failures.append(f"{key} expected={value!r} actual={observed!r}")
    return not failures, failures


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    root = args.root.resolve()
    policy_path = root / "validation" / "validation_policy.json"
    candidate_path = root / "src" / "reliability_core" / "validation_tiering.py"
    fixture_paths = {
        "migrated": root / "validation" / "fixtures" / "migrated_12.json",
        "adversarial": root / "validation" / "fixtures" / "adversarial_30.json",
        "residual": root / "validation" / "fixtures" / "residual_12.json",
    }
    policy = load_json(policy_path)
    policy_digest = hashlib.sha256(canonical_bytes(policy)).hexdigest()
    if policy_digest != EXPECTED_POLICY_DIGEST or policy.get("classifier_version") != EXPECTED_CLASSIFIER_VERSION:
        raise SystemExit("policy_authority_mismatch")
    suites = {name: load_json(path)["cases"] for name, path in fixture_paths.items()}
    counts = {name: len(cases) for name, cases in suites.items()}
    counts["total"] = sum(counts.values())
    if counts != EXPECTED_COUNTS:
        raise SystemExit("fixture_count_mismatch:" + json.dumps(counts, sort_keys=True))
    candidate = load_candidate(candidate_path)
    case_results = []
    passed = failed = exceptions = 0
    for suite, cases in suites.items():
        for case in cases:
            actual: dict[str, Any] = {}
            exception_text = None
            try:
                actual = run_operation(case, candidate, policy)
                exception_count = 0
            except Exception as exc:  # runner records; a case expecting zero fails
                exception_count = 1
                exceptions += 1
                exception_text = f"{type(exc).__name__}: {exc}"
            ok, mismatches = expected_matches(case["expected"], actual, exception_count)
            if ok:
                passed += 1
            else:
                failed += 1
            case_results.append({"suite": suite, "id": case["id"], "pass": ok, "expected": case["expected"], "actual": actual, "exception": exception_text, "mismatches": mismatches})
    fixture_digest = hashlib.sha256(canonical_bytes({name: load_json(path) for name, path in fixture_paths.items()})).hexdigest()
    result = {
        "status": "PASS" if failed == 0 and exceptions == 0 and passed == EXPECTED_COUNTS["total"] else "FAIL",
        "total": EXPECTED_COUNTS["total"], "passed": passed, "failed": failed, "exceptions": exceptions,
        "candidate_sha256": sha256_file(candidate_path), "policy_canonical_sha256": policy_digest,
        "fixture_bundle_sha256": fixture_digest, "counts": counts,
        "failures": [item for item in case_results if not item["pass"]],
    }
    rendered = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2)
    print(rendered)
    if args.output is not None:
        output = args.output.resolve()
        allowed_root = (root / "validation" / "results").resolve()
        if allowed_root != output.parent and allowed_root not in output.parents:
            raise SystemExit("output_must_be_under_results")
        output.parent.mkdir(parents=True, exist_ok=True)
        output.write_text(rendered + "\n", encoding="utf-8")
    return 0 if result["status"] == "PASS" else 1


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