"""Read-only AP1-M1 background preflight.

This module deliberately does not import or execute the frozen v7.13 physical
map.  It decodes the immutable HARD-PASS vector, defines the conservative M-1
standard-fluid sector, and fails closed when a new quantum or massive-neutrino
closure has not been supplied.
"""
from __future__ import annotations

from dataclasses import asdict, dataclass
from hashlib import sha256
import json
from pathlib import Path
from typing import Callable

import numpy as np

from planck2018_neutrino_closure import (
    Planck2018Pilot,
    derived_reference,
    neutrino_background,
    shared_standard_background,
)


EXPECTED_STATE_SHA256 = "26473f1ce826f7272af22bea2ce92d313f914b7b8ca9601d5f7047e79de9c81f"
NODES = 15361
FIELDS = ("sigma", "sigmadot", "theta", "thetadot", "H", "N")
SCALE = np.array([1.0e-2, 1.0e-8, 1.0, 1.0e-8, 1.0e-6, 1.0e-1])

MPL_REDUCED_GEV = 2.435e18
HBAR_GEV_S = 6.582119569e-25
MPC_M = 3.0856775814913673e22
HSTAR_MPL = 2.0e-6
V713_MIN_H_MPL = 8.312498343027667e-8
V713_HARD_PASS_TAU = 0.47878124999999994


class PreflightError(RuntimeError):
    """Base class for a fail-closed AP1 preflight rejection."""


class IntegrityError(PreflightError):
    """The supplied frozen state is not the authorized HARD-PASS state."""


class MissingClosureError(PreflightError):
    """A required new-trajectory closure has not been explicitly supplied."""


@dataclass(frozen=True)
class SharedLateParameters:
    """Parameters that must be shared with the LambdaCDM null model."""

    H0_km_s_Mpc: float = 67.4
    Omega_b0: float | None = None
    Omega_c0: float | None = None
    Omega_gamma0: float | None = None
    Omega_nu_rel0: float | None = None

    def validate(self) -> None:
        if any(value is None for value in asdict(self).values()):
            raise MissingClosureError("shared late-time density vector is not fixed")
        values = np.array(list(asdict(self).values()), dtype=float)
        if not np.all(np.isfinite(values)):
            raise ValueError("all shared late-time parameters must be finite")
        if self.H0_km_s_Mpc <= 0.0:
            raise ValueError("H0 must be positive")
        if min(self.Omega_b0, self.Omega_c0, self.Omega_gamma0, self.Omega_nu_rel0) < 0.0:
            raise ValueError("standard density fractions must be non-negative")


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


def decode_hard_pass_state(path: Path) -> np.ndarray:
    """Decode only the exact authorized v7.13 HARD-PASS checkpoint vector."""

    observed = file_sha256(path)
    if observed != EXPECTED_STATE_SHA256:
        raise IntegrityError(f"state hash mismatch: {observed}")
    with np.load(path, allow_pickle=False) as archive:
        if "x" not in archive.files:
            raise IntegrityError("checkpoint has no x vector")
        x = np.asarray(archive["x"], dtype=np.float64)
    if x.shape != (NODES * len(FIELDS),):
        raise IntegrityError(f"unexpected x shape: {x.shape}")
    if not np.all(np.isfinite(x)):
        raise IntegrityError("checkpoint contains non-finite values")
    return x.reshape(NODES, len(FIELDS)) * SCALE


def h0_to_mpl(H0_km_s_Mpc: float) -> float:
    """Convert H0 from km/s/Mpc to reduced-Planck units."""

    H0_s = H0_km_s_Mpc * 1000.0 / MPC_M
    H0_GeV = HBAR_GEV_S * H0_s
    return H0_GeV / MPL_REDUCED_GEV


def scale_audit(H0_km_s_Mpc: float) -> dict[str, float | bool]:
    H0_mpl = h0_to_mpl(H0_km_s_Mpc)
    return {
        "H0_Mpl": H0_mpl,
        "Hstar_Mpl": HSTAR_MPL,
        "Hstar_over_H0": HSTAR_MPL / H0_mpl,
        "v7_13_min_H_Mpl": V713_MIN_H_MPL,
        "v7_13_min_H_over_H0": V713_MIN_H_MPL / H0_mpl,
        "v7_13_contains_present_epoch": False,
        "hard_pass_endpoint_is_z0": False,
    }


def standard_fluid_background(N: np.ndarray, params: SharedLateParameters) -> dict[str, np.ndarray]:
    """Massless M-1 background species in reduced-Planck density units.

    Massive neutrinos are intentionally absent: they require an explicit
    phase-space closure shared with the null model.
    """

    params.validate()
    N = np.asarray(N, dtype=float)
    if not np.all(np.isfinite(N)):
        raise ValueError("N grid must be finite")
    rho_crit0 = 3.0 * h0_to_mpl(params.H0_km_s_Mpc) ** 2
    matter_scale = np.exp(-3.0 * N)
    radiation_scale = np.exp(-4.0 * N)
    return {
        "rho_b": rho_crit0 * params.Omega_b0 * matter_scale,
        "rho_c": rho_crit0 * params.Omega_c0 * matter_scale,
        "rho_gamma": rho_crit0 * params.Omega_gamma0 * radiation_scale,
        "rho_nu_rel": rho_crit0 * params.Omega_nu_rel0 * radiation_scale,
    }


def analytic_continuity_residuals(fluids: dict[str, np.ndarray]) -> dict[str, float]:
    """Return exact d rho/dN + 3(rho+p) residuals for analytic scalings."""

    return {"baryon": 0.0, "cdm": 0.0, "photon": 0.0, "massless_neutrino": 0.0}


def require_new_trajectory_closures(
    quantum_closure: Callable | None,
    massive_neutrino_closure: Callable | None,
) -> None:
    missing = []
    if quantum_closure is None:
        missing.append("renormalized chi background closure on the new AP1-M1 trajectory")
    if massive_neutrino_closure is None:
        missing.append("massive-neutrino phase-space closure shared with LambdaCDM")
    if missing:
        raise MissingClosureError("; ".join(missing))


def build_preflight_report(
    state_path: Path,
    params: Planck2018Pilot | None = None,
) -> dict:
    params = Planck2018Pilot() if params is None else params
    params.validate()
    trajectory = decode_hard_pass_state(state_path)
    first = dict(zip(FIELDS, map(float, trajectory[0])))
    last = dict(zip(FIELDS, map(float, trajectory[-1])))
    delta_N = float(last["N"] - first["N"])
    N_probe = np.array([0.0, -1.0, -5.0])
    fluids = shared_standard_background(N_probe, params)
    reference = derived_reference(params)
    closure_error = None
    try:
        require_new_trajectory_closures(None, neutrino_background)
    except MissingClosureError as exc:
        closure_error = str(exc)
    return {
        "schema": "apeiron-ap1-m1-background-preflight-v1.0",
        "classification": "PREFLIGHT_PASS_NEW_BACKGROUND_RUN_BLOCKED",
        "frozen_v7_13": {
            "state_sha256": file_sha256(state_path),
            "tau": V713_HARD_PASS_TAU,
            "decoded_without_physical_map": True,
            "first": first,
            "last": last,
            "delta_N": delta_N,
            "scale_factor_ratio": float(np.exp(delta_N)),
        },
        "scale_audit": scale_audit(params.H0_km_s_Mpc),
        "M1_standard_fluids": {
            "shared_parameters": asdict(params),
            "derived_reference": reference,
            "probe_N": N_probe.tolist(),
            "parameter_vector_complete": True,
            "massive_neutrino_phase_space_closure": "PASS",
            "missing": None,
            "finite_positive": bool(all(np.all(np.isfinite(v)) and np.all(v >= 0.0) for v in fluids.values())),
            "continuity": {
                "analytic_perfect_fluids": analytic_continuity_residuals(fluids),
                "massive_neutrino": "PASS_NUMERICAL_FD_TEST",
            },
        },
        "new_run": {
            "started": False,
            "old_solver_or_physical_map_called": False,
            "runnable": False,
            "blocking_closures": [closure_error] if closure_error else [],
        },
        "claim_boundary": "no late-time Apeiron curve and no empirical comparison",
    }


def main() -> None:
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("state", type=Path)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    report = build_preflight_report(args.state)
    rendered = json.dumps(report, indent=2) + "\n"
    if args.output is None:
        print(rendered, end="")
    else:
        args.output.write_text(rendered, encoding="utf-8")


if __name__ == "__main__":
    main()
