from __future__ import annotations

import datetime
import json
import os
import subprocess
import sys
from typing import Any


RUNTIME_DEPENDENCIES = {
    "numpy": "numpy>=1.24",
    "scipy": "scipy>=1.10",
    "pandas": "pandas>=2.0",
    "yaml": "PyYAML>=6.0",
}


def ensure_runtime_dependencies(root: str) -> None:
    """Make the canonical runner self-bootstrapping on a fresh Python install.

    The gate modules are executed in child Python processes, so this runner can
    safely install any missing third-party runtime packages before the first
    gate is launched. Existing packages are left untouched.
    """
    import importlib.util

    missing = [spec for module, spec in RUNTIME_DEPENDENCIES.items() if importlib.util.find_spec(module) is None]
    if not missing:
        print("UTFANSWF dependency preflight: PASS (all required Python packages available)")
        return

    print("UTFANSWF dependency preflight: missing " + ", ".join(missing))
    print("UTFANSWF dependency preflight: installing missing packages with this Python interpreter...")
    cmd = [
        sys.executable,
        "-m",
        "pip",
        "install",
        "--disable-pip-version-check",
        *missing,
    ]
    try:
        subprocess.check_call(cmd, cwd=root)
    except subprocess.CalledProcessError as exc:
        raise SystemExit(
            "UTFANSWF dependency bootstrap FAILED. "
            f"Command {' '.join(cmd)} exited with code {exc.returncode}. "
            "Check Python/pip and network/package-index access, then rerun the same canonical command."
        ) from exc

    importlib.invalidate_caches()
    unresolved = [module for module in RUNTIME_DEPENDENCIES if importlib.util.find_spec(module) is None]
    if unresolved:
        raise SystemExit(
            "UTFANSWF dependency bootstrap FAILED: still unavailable after pip install: "
            + ", ".join(unresolved)
        )
    print("UTFANSWF dependency preflight: PASS (missing packages installed)")


def utc_now_z() -> str:
    return datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds").replace("+00:00", "Z")


def load_json_file(path: str) -> dict[str, Any]:
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def normalize_result(row: float | int, gate_name: str, obj: dict[str, Any]) -> dict[str, Any]:
    """
    Normalize gate outputs from different schemas into one common report schema.
    Handles foundational preflights, hardened Row 19 output, and later harness outputs.
    """
    out = dict(obj)

    out["row"] = row
    out["gate"] = out.get("gate") or gate_name
    out["context"] = out.get("context") or "LIVE"
    out["verdict"] = out.get("verdict") or "UNKNOWN"
    out["timestamp_utc"] = out.get("timestamp_utc") or utc_now_z()

    notes = out.get("notes", "")
    if isinstance(notes, list):
        notes = "; ".join(str(x) for x in notes)
    elif notes is None:
        notes = ""
    elif not isinstance(notes, str):
        notes = str(notes)

    if row in (0, 0.1, 0.2, 0.3, 0.4):
        out["tier"] = out.get("tier") or "Critical"
        out["blocking"] = out.get("blocking")
        if out["blocking"] is None:
            out["blocking"] = True
        if not notes.strip():
            notes = "Foundational preflight and declared downstream handoff."
    elif row == 19:
        out["tier"] = out.get("tier") or "Critical"
        out["blocking"] = out.get("blocking")
        if out["blocking"] is None:
            out["blocking"] = True
        if not notes.strip():
            notes = "Row 19 hardened FRG / PPN-GW benchmark / positivity handoff / cosmology-derivative gate."
    elif row == 20:
        out["tier"] = out.get("tier") or "Critical"
        out["blocking"] = out.get("blocking")
        if out["blocking"] is None:
            out["blocking"] = True
    elif row == 26:
        out["tier"] = out.get("tier") or "Informational"
        out["blocking"] = out.get("blocking")
        if out["blocking"] is None:
            out["blocking"] = False
    else:
        out["tier"] = out.get("tier") or "Diagnostic"
        out["blocking"] = out.get("blocking")
        if out["blocking"] is None:
            out["blocking"] = False

    out["notes"] = notes
    return out


def error_result(row: float | int, gate_name: str, notes: str) -> dict[str, Any]:
    critical = row in (0, 0.1, 0.2, 0.3, 0.4, 19, 20)
    return {
        "row": row,
        "gate": gate_name,
        "context": "LIVE",
        "tier": "Critical" if critical else "Diagnostic",
        "verdict": "ERROR",
        "blocking": critical,
        "timestamp_utc": utc_now_z(),
        "notes": notes,
    }


def find_row20_result(results: list[dict[str, Any]]) -> dict[str, Any] | None:
    for result in results:
        if result.get("row") == 20:
            return result
    return None


def find_row_result(results: list[dict[str, Any]], row: float | int) -> dict[str, Any] | None:
    for result in results:
        if result.get("row") == row:
            return result
    return None


def row20_chi2_per_dof(row20: dict[str, Any] | None) -> float | None:
    if not row20:
        return None
    summary = row20.get("summary", {})
    value = summary.get("chi2_per_dof")
    if value is None:
        return None
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def append_derived_cosmology_section(report_lines: list[str], results: list[dict[str, Any]]) -> None:
    """
    Report the historical v22 and extended post-release H0 records without affecting
    Row 20 gate logic. The extended value is read from Row 20.1 rather than hard-coded.
    """
    row20 = find_row20_result(results)
    row20_ext = find_row_result(results, 20.1)
    chi2_dof = row20_chi2_per_dof(row20)

    report_lines.append("")
    report_lines.append("---")
    report_lines.append("")
    report_lines.append("## Cosmology Result and Preserved H0 Record (Row 20 + 20.1)")
    report_lines.append("")
    report_lines.append(
        "Row 20 remains the critical compressed-cosmology gate. Row 20.1 preserves the "
        "July 26, 2026 post-release distributed-H0 master record, including adverse hardening tests."
    )
    report_lines.append("")

    if chi2_dof is not None:
        row20_verdict = str((row20 or {}).get("verdict", "UNKNOWN"))
        threshold = (row20 or {}).get("summary", {}).get("threshold_chi2_per_dof", 1.25)
        comparison = "within" if chi2_dof <= float(threshold) else "above"
        report_lines.append(
            f"- Row 20 hardened core CMB+BAO score: χ²/dof ≈ {chi2_dof:.3f}; "
            f"{row20_verdict} ({comparison} frozen threshold ≤ {float(threshold):.2f})."
        )
    else:
        report_lines.append("- Row 20 compressed cosmology fit: see Row 20 result.")

    if row20_ext and row20_ext.get("verdict") == "PASS":
        published = row20_ext.get("published_v22", {})
        extended = row20_ext.get("extended_candidate", {})
        hardening = row20_ext.get("retrospective_hardening", {})
        mandatory = hardening.get("mandatory_construction_subset", {})
        heldout = hardening.get("heldout_DESI", {})
        localized = hardening.get("localized_z0706", {})

        report_lines.append(
            f"- Historical published v22 distributed result: ≈ {float(published.get('H0_distributed_approx_km_s_Mpc', 72.7)):.1f} km/s/Mpc."
        )
        report_lines.append(
            f"- Preserved July-2026 post-release distributed candidate (not active v23 derived H0): {float(extended.get('H0_distributed_km_s_Mpc')):.12f} km/s/Mpc."
        )
        report_lines.append(
            f"- Mandatory frozen construction subset: {mandatory.get('verdict')} "
            f"(p ≈ {float(mandatory.get('recomputed_p_value')):.6g})."
        )
        report_lines.append(
            f"- Held-out DESI subset: {heldout.get('verdict')} "
            f"(p ≈ {float(heldout.get('recomputed_p_value')):.6g})."
        )
        report_lines.append(
            f"- Localized z=0.706 construction stress: {localized.get('verdict')} "
            f"(p ≈ {float(localized.get('recomputed_p_value')):.6g})."
        )
        report_lines.append("")
        report_lines.append("Interpretation:")
        report_lines.append(
            "- The 73.134202 record is preserved as the July-2026 distributed/channel-node candidate; it is not the active v23 derived H0 after Row 20 moved to direct correlated BAO scoring."
        )
        report_lines.append(
            "- The single-global-H0 substitution is preserved as a catastrophic-fail negative control."
        )
        report_lines.append(
            "- The post-hoc BAO-node pass interval is diagnostic only and is not promoted to a derived node."
        )
        report_lines.append(
            "- The official observable-space Jacobian/covariance map is now explicit, but the next BAO channel node must still be independently derived; only then may a new distributed H0 be recalculated."
        )
        report_lines.append("")
        report_lines.append("Status:")
        report_lines.append(
            "- July master record preserved/reproduced with mandatory adverse tests explicit; it is a historical/post-release candidate record, not the active v23 cosmology verdict."
        )
    else:
        report_lines.append("- Extended H0 master record: unavailable or failed integrity gate; no extended value promoted.")


def main() -> None:
    root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
    ensure_runtime_dependencies(root)
    results_dir = os.path.join(root, "results")
    os.makedirs(results_dir, exist_ok=True)

    ledger_path = os.path.join(results_dir, "ledger.jsonl")
    results_path = os.path.join(results_dir, "results.json")
    report_path = os.path.join(results_dir, "REPORT.md")

    gates = [
        {
            "row": 0,
            "gate": "zero_state_foundation",
            "cmd": [sys.executable, "-m", "harness.gate_zs_foundation"],
            "output": os.path.join(root, "results", "zs_foundation", "result.json"),
        },
        {
            "row": 0.1,
            "gate": "swf_ism_information_state_foundation",
            "cmd": [sys.executable, "-m", "harness.gate_swf_ism_foundation"],
            "output": os.path.join(root, "results", "swf_ism_foundation", "result.json"),
        },
        {
            "row": 0.2,
            "gate": "answf_eft_positivity",
            "cmd": [sys.executable, "-m", "harness.gate_answf_eft"],
            "output": os.path.join(root, "results", "answf_eft", "result.json"),
        },
        {
            "row": 0.3,
            "gate": "sanswf_symmetry_stability",
            "cmd": [sys.executable, "-m", "harness.gate_sanswf_symmetry"],
            "output": os.path.join(root, "results", "sanswf_symmetry", "result.json"),
        },
        {
            "row": 0.4,
            "gate": "utfanswf_unified_embedding",
            "cmd": [sys.executable, "-m", "harness.gate_utfanswf_embedding"],
            "output": os.path.join(root, "results", "utfanswf_embedding", "result.json"),
        },
        {
            "row": 19,
            "gate": "row19_core_math",
            "cmd": [sys.executable, "-m", "tools.row19.run_row19"],
            "output": os.path.join(root, "results", "row19_results.json"),
        },
        {
            "row": 20,
            "gate": "compressed_cosmology_fit",
            "cmd": [sys.executable, "-m", "harness.gate_row20_cosmology"],
            "output": os.path.join(root, "results", "row20_cosmology", "result.json"),
        },
        {
            "row": 20.1,
            "gate": "h0_extended_distributed_master_record",
            "cmd": [sys.executable, "-m", "harness.gate_row20_h0_extended"],
            "output": os.path.join(root, "results", "row20_h0_extended", "result.json"),
        },
        {
            "row": 20.4,
            "gate": "jwst_uvlf_diagnostic_record",
            "cmd": [sys.executable, "-m", "harness.gate_row20_4_jwst_diagnostic"],
            "output": os.path.join(root, "results", "row20_4_jwst", "result.json"),
        },
        {
            "row": 21,
            "gate": "axion_dark_matter_gate",
            "cmd": [sys.executable, "-m", "harness.gate_row21_axion"],
            "output": os.path.join(root, "results", "row21_axion", "result.json"),
        },
        {
            "row": 22,
            "gate": "neutral_zone_sandwich",
            "cmd": [sys.executable, "-m", "harness.gate_row22_nz"],
            "output": os.path.join(root, "results", "row22_nz", "result.json"),
        },
        {
            "row": 23,
            "gate": "bh_ringdown_echoes",
            "cmd": [sys.executable, "-m", "harness.gate_row23_ringdown"],
            "output": os.path.join(root, "results", "row23_ringdown", "result.json"),
        },
        {
            "row": 24,
            "gate": "ai_utfanswf_guardrails",
            "cmd": [sys.executable, "-m", "harness.gate_row24_ai"],
            "output": os.path.join(root, "results", "row24_ai", "result.json"),
        },
        {
            "row": 25,
            "gate": "break_utfanswf_stress_registry",
            "cmd": [sys.executable, "-m", "harness.gate_row25_stress"],
            "output": os.path.join(root, "results", "row25_stress", "result.json"),
        },
        {
            "row": 26,
            "gate": "references_acknowledgments_integrity",
            "cmd": [sys.executable, "-m", "harness.gate_row26_refs"],
            "output": os.path.join(root, "results", "row26_refs", "result.json"),
        },
        {
            "row": 27,
            "gate": "fine_structure_blind_checkpoint",
            "cmd": [sys.executable, "-m", "harness.gate_row27_fine_structure"],
            "output": os.path.join(root, "results", "row27_fine_structure", "result.json"),
        },
        {
            "row": 28,
            "gate": "proton_stability_z6",
            "cmd": [sys.executable, "-m", "harness.gate_row28_proton_stability"],
            "output": os.path.join(root, "results", "row28_proton_stability", "result.json"),
        },
        {
            "row": 29,
            "gate": "proton_origin_target_isolated_mass_closure",
            "cmd": [sys.executable, "-m", "harness.gate_row29_proton_origin"],
            "output": os.path.join(root, "results", "row29_proton_origin", "result.json"),
        },
    ]

    all_results: list[dict[str, Any]] = []

    for gate in gates:
        row = gate["row"]
        gate_name = gate["gate"]
        cmd = gate["cmd"]
        output_path = gate["output"]

        try:
            subprocess.check_call(cmd, cwd=root)
        except subprocess.CalledProcessError as e:
            all_results.append(
                error_result(
                    row,
                    gate_name,
                    f"Gate execution failed: {' '.join(cmd)} exited with code {e.returncode}",
                )
            )
            continue
        except Exception as e:
            all_results.append(
                error_result(
                    row,
                    gate_name,
                    f"Gate execution raised exception: {e}",
                )
            )
            continue

        if not os.path.exists(output_path):
            all_results.append(
                error_result(
                    row,
                    gate_name,
                    f"Expected output missing: {os.path.relpath(output_path, root)}",
                )
            )
            continue

        try:
            obj = load_json_file(output_path)
            all_results.append(normalize_result(row, gate_name, obj))
        except Exception as e:
            all_results.append(
                error_result(
                    row,
                    gate_name,
                    f"Failed reading output JSON {os.path.relpath(output_path, root)}: {e}",
                )
            )

    all_results.sort(key=lambda r: r.get("row", 999))

    # Post-release Frog 8/8 research-overlay integrity check. This is deliberately
    # auxiliary rather than a new numbered canonical gate, so the historical
    # 18-gate count and row semantics remain unchanged.
    frog_verifier = os.path.join(root, "research", "frog_closeout", "verify_frog_closeout.py")
    frog_output = os.path.join(root, "results", "frog_closeout", "result.json")
    frog_check: dict[str, Any]
    try:
        subprocess.check_call([sys.executable, frog_verifier], cwd=root)
        frog_check = load_json_file(frog_output)
    except Exception as e:
        frog_check = {
            "gate": "frog_8_of_8_research_overlay_integrity",
            "context": "POST_RELEASE_RESEARCH",
            "tier": "Auxiliary integrity",
            "blocking": True,
            "verdict": "ERROR",
            "notes": f"Frog overlay verifier failed: {e}",
        }

    # Authoritative Aug-19 prediction reproduction is also auxiliary rather than
    # a new numbered gate. It executes under the same one-command harness.
    prediction_reproducer = os.path.join(root, "research", "new_predictions", "reproduce_new_predictions.py")
    prediction_output = os.path.join(root, "results", "new_predictions", "result.json")
    prediction_check: dict[str, Any]
    try:
        subprocess.check_call([sys.executable, prediction_reproducer], cwd=root)
        prediction_check = load_json_file(prediction_output)
    except Exception as e:
        prediction_check = {
            "gate": "new_predictions_2026_08_19_reproduction",
            "context": "POST_RELEASE_RESEARCH",
            "tier": "Auxiliary prediction reproduction",
            "blocking": True,
            "verdict": "ERROR",
            "notes": f"New-prediction reproducer failed: {e}",
        }

    auxiliary_checks = [frog_check, prediction_check]

    with open(ledger_path, "w", encoding="utf-8") as ledger:
        for r in all_results:
            entry = {
                "ts_utc": utc_now_z(),
                "row": r.get("row"),
                "gate": r.get("gate"),
                "context": r.get("context", "LIVE"),
                "tier": r.get("tier"),
                "verdict": r.get("verdict"),
                "blocking": r.get("blocking", False),
                "notes": r.get("notes", ""),
            }
            ledger.write(json.dumps(entry, sort_keys=True) + "\n")
        for aux in auxiliary_checks:
            entry = {
                "ts_utc": utc_now_z(),
                "row": None,
                "gate": aux.get("gate"),
                "context": aux.get("context", "POST_RELEASE_RESEARCH"),
                "tier": aux.get("tier"),
                "verdict": aux.get("verdict"),
                "blocking": aux.get("blocking", True),
                "notes": aux.get("notes", ""),
            }
            ledger.write(json.dumps(entry, sort_keys=True) + "\n")

    blocking_failures = [
        r for r in all_results
        if bool(r.get("blocking", False)) and str(r.get("verdict", "")).upper() != "PASS"
    ]
    auxiliary_failures = [
        a for a in auxiliary_checks
        if bool(a.get("blocking", True)) and str(a.get("verdict", "")).upper() != "PASS"
    ]
    overall_verdict = "PASS" if (not blocking_failures and not auxiliary_failures) else "FAIL"

    results_obj = {
        "run_utc": utc_now_z(),
        "overall_verdict": overall_verdict,
        "blocking_failure_rows": [r.get("row") for r in blocking_failures],
        "canonical_gate_count": len(all_results),
        "auxiliary_checks": auxiliary_checks,
        "results": all_results,
    }
    with open(results_path, "w", encoding="utf-8") as f:
        json.dump(results_obj, f, indent=2, sort_keys=True)

    report_lines: list[str] = []
    report_lines.append("# UTFANSWF v23 — Gate Run Report")
    report_lines.append("")
    report_lines.append(f"Run (UTC): {utc_now_z()}")
    report_lines.append("")
    report_lines.append("| Row | Gate | Tier | Verdict | Blocking | Notes |")
    report_lines.append("|---:|---|---|---|---|---|")

    for r in all_results:
        row = r.get("row", "")
        gate_name = str(r.get("gate", "")).replace("|", "\\|")
        tier = str(r.get("tier", "")).replace("|", "\\|")
        verdict = str(r.get("verdict", "")).replace("|", "\\|")
        blocking = str(r.get("blocking", False)).replace("|", "\\|")
        notes = str(r.get("notes", "")).replace("|", "\\|")
        report_lines.append(f"| {row} | {gate_name} | {tier} | {verdict} | {blocking} | {notes} |")

    append_derived_cosmology_section(report_lines, all_results)

    report_lines.append("")
    report_lines.append("---")
    report_lines.append("")
    report_lines.append("## Frog 8/8 Research-Overlay Integrity")
    report_lines.append("")
    report_lines.append(f"- Auxiliary verdict: {frog_check.get('verdict', 'UNKNOWN')}")
    report_lines.append("- Canonical gate count remains 18; this auxiliary check is not a numbered physics gate.")
    report_lines.append("- Research-board status: 8/8 GREEN at the derivation level.")
    report_lines.append("- Items 01 and 05: one target-free QCD electromagnetic generating functional; production-scale NP-QCD numerical execution and independent verification remain pending.")
    report_lines.append("- Protected main.tex and main.pdf are hash-locked by the auxiliary verifier.")

    report_lines.append("")
    report_lines.append("## Aug. 19 New-Prediction Reproduction")
    report_lines.append("")
    report_lines.append(f"- Auxiliary verdict: {prediction_check.get('verdict', 'UNKNOWN')}")
    report_lines.append("- Executes under the same `python -m harness.run_all_gates` command.")
    report_lines.append("- NP-01: fixed-t0 RGI light-quark -> nucleon-mass response, including beta-block robustness.")
    report_lines.append("- NP-02: exact-Z6 DeltaB selection; DeltaB=1,2 forbidden and DeltaB=3 first allowed at dimension 15.")
    report_lines.append("- NP-03: PSN-v2 split-spectrum one-loop gauge-running thresholds and electromagnetic kink signature.")
    report_lines.append("- Demoted/circular morning candidates remain excluded from the promoted prediction set.")

    with open(report_path, "w", encoding="utf-8") as f:
        f.write("\n".join(report_lines) + "\n")

    print("Wrote:")
    print(f"  {os.path.relpath(results_path, root)}")
    print(f"  {os.path.relpath(ledger_path, root)}")
    print(f"  {os.path.relpath(report_path, root)}")
    print(f"Overall blocking verdict: {overall_verdict}")
    if blocking_failures:
        print("Blocking failure rows:", ", ".join(str(r.get("row")) for r in blocking_failures))
    if auxiliary_failures:
        print("Blocking auxiliary failures:", ", ".join(str(a.get("gate")) for a in auxiliary_failures))
    if blocking_failures or auxiliary_failures:
        raise SystemExit(1)


if __name__ == "__main__":
    main()