from __future__ import annotations

import datetime
import hashlib
import json
import math
from pathlib import Path

try:
    from scipy.stats import chi2
except Exception:  # pragma: no cover
    chi2 = None

ROOT = Path(__file__).resolve().parents[1]
RESEARCH = ROOT / "research" / "h0_extended"
FROZEN = RESEARCH / "H0_EXTENDED_FROZEN_RECORD.json"
SOURCE = RESEARCH / "UTFANSWF_H0_Master_Record_v1.txt"
OUT_DIR = ROOT / "results" / "row20_h0_extended"
OUT = OUT_DIR / "result.json"
ROW20_RESULT = ROOT / "results" / "row20_cosmology" / "result.json"


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


def sha256(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for block in iter(lambda: f.read(1024 * 1024), b""):
            h.update(block)
    return h.hexdigest()


def close(a: float, b: float, tol: float = 1e-10) -> bool:
    return math.isclose(float(a), float(b), rel_tol=0.0, abs_tol=tol)


def sf(value: float, dof: int) -> float | None:
    if chi2 is None:
        return None
    return float(chi2.sf(value, dof))


def main() -> None:
    record = json.loads(FROZEN.read_text(encoding="utf-8"))
    text = SOURCE.read_text(encoding="utf-8")
    row20 = json.loads(ROW20_RESULT.read_text(encoding="utf-8")) if ROW20_RESULT.exists() else {}
    row20_summary = row20.get("summary", {})
    row20_bridge = row20_summary.get("v22_bridge", {})

    ext = record["extended_construction"]
    primary = record["primary_ACT_complete_DESI"]
    neg = record["required_negative_control"]
    hold = record["frozen_retrospective_holdout"]
    scan = record["posthoc_BAO_scan"]
    boundary = record["claim_boundary"]

    source_hash = sha256(SOURCE)
    expected_hash = record["source_sha256"]

    bao_node_recomputed = ext["early_ACT_CMB_node_km_s_Mpc"] + ext["bao_shift_exact_km_s_Mpc"]
    pre_sn_recomputed = (
        ext["historical_DESI_BAO_node_km_s_Mpc"]
        + ext["cmb_output_shift_km_s_Mpc"]
        + ext["gw_output_shift_km_s_Mpc"]
    )
    final_recomputed = pre_sn_recomputed + ext["sn_geometry_shift_exact_km_s_Mpc"]
    distance_recomputed = abs(ext["declared_local_target_km_s_Mpc"] - final_recomputed)

    primary_p = sf(primary["combined_objective"], primary["reference_dof"])
    training_p = sf(hold["training_objective"], hold["training_dof"])
    heldout_p = sf(hold["heldout_chi2"], hold["heldout_observables"])
    aggregate_p = sf(hold["aggregate_statistic"], hold["aggregate_dof"])
    z0706_p = sf(hold["localized_z0706_chi2"], hold["localized_z0706_dof"])

    checks = {
        "source_master_record_present": SOURCE.is_file(),
        "source_master_record_sha256_matches": source_hash == expected_hash,
        "source_preserves_favorable_and_adverse_status": all(
            phrase in text
            for phrase in (
                "PASS_CANDIDATE",
                "CATASTROPHIC FAIL",
                "Training p-value:                             0.0177795",
                "Verdict:                                      STRONG PASS",
            )
        ),
        "historical_bao_node_recomputes": close(
            bao_node_recomputed, ext["historical_DESI_BAO_node_km_s_Mpc"], 1e-12
        ),
        "pre_sn_output_recomputes": close(
            pre_sn_recomputed, ext["pre_SN_output_exact_km_s_Mpc"], 1e-12
        ),
        "final_distributed_output_recomputes": close(
            final_recomputed, ext["final_distributed_output_km_s_Mpc"], 1e-12
        ),
        "distance_from_target_recomputes": close(
            distance_recomputed, ext["distance_from_target_km_s_Mpc"], 1e-12
        ),
        "declared_target_gate_recomputes": distance_recomputed <= ext["declared_tolerance_km_s_Mpc"],
        "primary_objective_is_sum_of_ACT_and_DESI": close(
            primary["ACT_mahalanobis_squared"] + primary["DESI_chi2"],
            primary["combined_objective"],
            1e-9,
        ),
        "primary_p_value_recomputes": primary_p is not None and close(primary_p, primary["reference_p_value"], 5e-7),
        "primary_candidate_threshold_passes": primary_p is not None and primary_p >= 0.05,
        "mandatory_training_subset_failure_preserved": training_p is not None and training_p < hold["training_threshold_p"] and hold["training_verdict"] == "FAIL",
        "training_p_value_recomputes": training_p is not None and close(training_p, hold["training_p_value"], 5e-7),
        "heldout_strong_pass_preserved": heldout_p is not None and heldout_p >= 0.05 and hold["heldout_max_abs_pull"] <= hold["heldout_max_pull_threshold"] and hold["heldout_verdict"] == "STRONG_PASS",
        "heldout_p_value_recomputes": heldout_p is not None and close(heldout_p, hold["heldout_p_value"], 5e-7),
        "aggregate_context_p_value_recomputes": aggregate_p is not None and close(aggregate_p, hold["aggregate_p_value"], 5e-7),
        "localized_z0706_failure_preserved": z0706_p is not None and z0706_p < 0.05 and hold["localized_z0706_verdict"] == "FAIL",
        "localized_z0706_p_value_recomputes": z0706_p is not None and close(z0706_p, hold["localized_z0706_p_value"], 5e-7),
        "single_global_H0_negative_control_preserved": neg["must_be_preserved"] is True and neg["verdict"] == "CATASTROPHIC_FAIL" and neg["max_heldout_pull"] > 3.0,
        "posthoc_scan_not_promoted_to_derived_node": scan["may_replace_historical_node"] is False and scan["independent_derivation_required"] is True,
        "extended_output_not_global_background": boundary["single_global_substitution_allowed"] is False,
        "target_free_prediction_not_claimed": boundary["target_free_prediction"] is False,
        "independent_validation_not_claimed": boundary["independent_validation"] is False,
        "hubble_tension_resolution_not_overclaimed": boundary["hubble_tension_proven_resolved"] is False,
        "next_correlated_derivation_remains_open": boundary["next_correlated_derivation_open"] is True and boundary["must_recalculate_after_next_BAO_derivation"] is True,
        "active_v23_row20_uses_core_CMB_BAO_score_without_proxy_dilution": bool(
            row20.get("verdict") in {"PASS", "FAIL"}
            and row20_summary.get("critical_evidence_blocks") == ["CMB", "BAO_DESI_DR2_FULL"]
            and row20_summary.get("diagnostic_only_blocks") == ["BAO_DV", "BAO_DM_H", "SN", "GW"]
            and "full official DESI DR2 native Gaussian BAO likelihood" in str(row20_summary.get("mode", ""))
            and int(row20_summary.get("n_fitted", -1)) == 3
            and int(row20_summary.get("effective_dof", -1)) == 13
        ),
        "historical_residual_bridge_not_used_in_active_v23_score": bool(
            row20_bridge.get("BAO_DM_H") == "historical residual-derived NZ/CFGL correction retained only as diagnostic"
        ),
    }

    all_pass = all(checks.values())
    verdict = "PASS" if all_pass else "FAIL"
    status = (
        "PASS_H0_MASTER_RECORD_PRESERVED__NOT_ACTIVE_V23_DERIVATION__NEXT_DISTRIBUTED_NODE_OPEN"
        if all_pass
        else "FAIL_H0_MASTER_RECORD_INTEGRITY_OR_BOUNDARY"
    )

    result = {
        "row": 20.1,
        "gate": "h0_extended_distributed_master_record",
        "context": "POST_RELEASE_RESEARCH",
        "tier": "Diagnostic",
        "blocking": False,
        "timestamp_utc": utc_now_z(),
        "verdict": verdict,
        "status": status,
        "checks": checks,
        "published_v22": {
            "H0_distributed_approx_km_s_Mpc": record["published_v22_h0_approx_km_s_Mpc"],
            "role": "historical published v22 result; preserved, not overwritten",
        },
        "extended_candidate": {
            "H0_distributed_km_s_Mpc": ext["final_distributed_output_km_s_Mpc"],
            "target_km_s_Mpc": ext["declared_local_target_km_s_Mpc"],
            "tolerance_km_s_Mpc": ext["declared_tolerance_km_s_Mpc"],
            "distance_from_target_km_s_Mpc": distance_recomputed,
            "historical_role": boundary["extended_output_role"],
            "v23_active_role": "PRESERVED_POST_RELEASE_CANDIDATE_RECORD__NOT_CURRENT_DERIVED_H0",
            "reason": "The active v23 Row 20 score no longer uses the residual-derived BAO bridge that fed the July distributed chain. A new distributed H0 must be recalculated only after an independently derived BAO channel node exists.",
        },
        "active_v23_cosmology": {
            "source": "results/row20_cosmology/result.json",
            "verdict": row20.get("verdict"),
            "chi2_total": row20_summary.get("chi2_total"),
            "n_observables": row20_summary.get("n_total"),
            "n_fitted": row20_summary.get("n_fitted"),
            "n_eff": row20_summary.get("effective_dof"),
            "chi2_per_dof": row20_summary.get("chi2_per_dof"),
            "threshold_chi2_per_dof": row20_summary.get("threshold_chi2_per_dof"),
            "mode": row20_summary.get("mode"),
            "distributed_H0_output": "OPEN_PENDING_INDEPENDENT_BAO_NODE_DERIVATION",
        },
        "construction_chain": {
            "ACT_CMB_anchor": ext["early_ACT_CMB_node_km_s_Mpc"],
            "BAO_shift_exact": ext["bao_shift_exact_km_s_Mpc"],
            "DESI_BAO_node": ext["historical_DESI_BAO_node_km_s_Mpc"],
            "CMB_output_shift": ext["cmb_output_shift_km_s_Mpc"],
            "GW_output_shift": ext["gw_output_shift_km_s_Mpc"],
            "pre_SN_output": pre_sn_recomputed,
            "SN_geometry_shift": ext["sn_geometry_shift_exact_km_s_Mpc"],
            "final_distributed_output": final_recomputed,
        },
        "primary_correlated_result": {
            **primary,
            "recomputed_p_value": primary_p,
        },
        "retrospective_hardening": {
            "mandatory_construction_subset": {
                "objective": hold["training_objective"],
                "dof": hold["training_dof"],
                "recomputed_p_value": training_p,
                "verdict": hold["training_verdict"],
            },
            "heldout_DESI": {
                "chi2": hold["heldout_chi2"],
                "dof": hold["heldout_observables"],
                "recomputed_p_value": heldout_p,
                "max_abs_pull": hold["heldout_max_abs_pull"],
                "verdict": hold["heldout_verdict"],
            },
            "localized_z0706": {
                "chi2": hold["localized_z0706_chi2"],
                "dof": hold["localized_z0706_dof"],
                "recomputed_p_value": z0706_p,
                "verdict": hold["localized_z0706_verdict"],
            },
            "required_negative_control": neg,
            "posthoc_scan": scan,
        },
        "claim_boundary": boundary,
        "source": {
            "master_record": str(SOURCE.relative_to(ROOT)).replace("\\", "/"),
            "master_record_sha256": source_hash,
            "frozen_record": str(FROZEN.relative_to(ROOT)).replace("\\", "/"),
        },
        "open_gate": (
            "Derive any revised BAO channel node independently in the official correlated D_M/r_d and D_H/r_d space; "
            "use the now-explicit official D_M/r_d and D_H/r_d Jacobian/covariance map with shared Omega_m and r_d; derive the BAO channel node independently; recalculate every downstream H0 "
            "quantity; freeze and rerun all mandatory construction, holdout, leave-one-out, channel, joint, and negative-control tests."
        ),
        "notes": (
            "PASS means the July 26, 2026 H0 Master Record v1 is preserved and internally reproduced, including adverse tests. "
            "It does not make 73.134202 the active v23 derived H0. The hardened v23 Row 20 Critical score is reported independently (and may PASS or FAIL under its own frozen threshold); a new distributed H0 remains open until the BAO node is derived without the historical residual bridge."
        ),
    }

    OUT_DIR.mkdir(parents=True, exist_ok=True)
    OUT.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    print(json.dumps(result, indent=2, sort_keys=True))

    if not all_pass:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
