"""Fail-closed multiscale architecture preflight for the AP1-M1 background.

The chronology-safe 128-shell junction seed spans almost 57 e-folds to the
shared present-day anchor.  This module quantifies the resulting numerical
dynamic range before a physical background candidate is attempted.  Its one
ODE integration deliberately holds the initial quantum triplet fixed and is
therefore an architecture diagnostic only, never a background approximation
or candidate.
"""
from __future__ import annotations

from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from hashlib import sha256
import json
from pathlib import Path

import numpy as np
from scipy.integrate import solve_ivp

from ap1_r2c_self_consistent_candidate import classical_terms, standard_stress
from chi_background_closure import (
    ChiParameters,
    ChiTrajectory,
    TrajectoryError,
    curvature_uv_tail_n,
    portal_terms,
)
from planck2018_neutrino_closure import Planck2018Pilot, derived_reference


EXPECTED_AUTHORITIES = {
    "AP1/APEIRON_AP1_M1_BACKGROUND_SPEC_LATEST.md": (
        "9a565aef648f4a7229941bf9efa5c51a754fd4763dd11e59e8032701d6ed0259"
    ),
    "AP1/APEIRON_AP1_M1_JUNCTION_128_SEED_CHECKPOINT_LATEST.json": (
        "cf946bfcde4c10a36d5eee99a18fe1a8745483e111304d52f4cc17b3fdf103b4"
    ),
    "AP1/APEIRON_AP1_M1_JUNCTION_128_SEED_LATEST.npz": (
        "644126925fcff32df6f881ee604787751dbe48829072671bf541552bd65dc729"
    ),
    "AP1/CODE/ap1_m1_junction_128_seed.py": (
        "ede27847eaddf50fbd8c2f34c6aa634e993cf106780be45f8bbc62b8e3e7a1a6"
    ),
    "AP1/CODE/ap1_r2c_self_consistent_candidate.py": (
        "2c22454f69817ecdc40d38dacad0efaa72fdddcfa50c9c00a48478d610ed8cb0"
    ),
    "AP1/CODE/chi_background_closure.py": (
        "1097888c72fbe9152e1905a2a3ab37c15df9ca681639571b87d76de6ddf072a7"
    ),
    "AP1/CODE/planck2018_neutrino_closure.py": (
        "ccd3158930dcb9ab74d7634ed06f2eaa00745ac95e2883f9fe02d9039a056acc"
    ),
}


class MultiscalePreflightError(RuntimeError):
    """The chronology-safe seed or architecture diagnostic is incomplete."""


@dataclass(frozen=True)
class DiagnosticConfig:
    nodes: int = 1025
    rtol: float = 2.0e-9
    max_step_N: float = 0.04

    def validate(self) -> None:
        if self.nodes < 257 or (self.nodes - 1) & (self.nodes - 2):
            raise ValueError("2^p+1 diagnostic nodes required")
        if not 0.0 < self.rtol <= 1.0e-7:
            raise ValueError("bounded diagnostic relative tolerance required")
        if not 0.0 < self.max_step_N <= 0.05:
            raise ValueError("bounded diagnostic N step required")


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


def array_sha256(value: np.ndarray) -> str:
    array = np.ascontiguousarray(value)
    digest = sha256()
    digest.update(array.dtype.str.encode("ascii"))
    digest.update(str(array.shape).encode("ascii"))
    digest.update(array.tobytes())
    return digest.hexdigest()


def load_seed(apeiron_root: Path) -> tuple[dict, dict[str, np.ndarray], dict[str, str]]:
    observed = {
        name: file_sha256(apeiron_root / name) for name in EXPECTED_AUTHORITIES
    }
    if observed != EXPECTED_AUTHORITIES:
        changed = [
            name
            for name in EXPECTED_AUTHORITIES
            if observed.get(name) != EXPECTED_AUTHORITIES[name]
        ]
        raise MultiscalePreflightError(f"authority drift: {changed}")
    report_path = (
        apeiron_root
        / "AP1/APEIRON_AP1_M1_JUNCTION_128_SEED_CHECKPOINT_LATEST.json"
    )
    binary_path = apeiron_root / "AP1/APEIRON_AP1_M1_JUNCTION_128_SEED_LATEST.npz"
    report = json.loads(report_path.read_text(encoding="utf-8"))
    if report.get("classification") != "M1_JUNCTION_128_SEED_PASS_BACKGROUND_PILOT_OPEN":
        raise MultiscalePreflightError("128-shell seed classification is not PASS")
    if not report.get("all_seed_gates_pass") or not report.get("seed_released"):
        raise MultiscalePreflightError("128-shell seed gates are incomplete")
    if report.get("new_AP1_M1_background_started"):
        raise MultiscalePreflightError("seed authority already claims a background run")
    if report["seed_npz"]["sha256"] != observed[
        "AP1/APEIRON_AP1_M1_JUNCTION_128_SEED_LATEST.npz"
    ]:
        raise MultiscalePreflightError("seed report/binary hash mismatch")
    with np.load(binary_path, allow_pickle=False) as archive:
        arrays = {name: np.asarray(archive[name]) for name in archive.files}
    if set(arrays) != set(report["array_sha256"]):
        raise MultiscalePreflightError("seed array inventory mismatch")
    for name, expected in report["array_sha256"].items():
        if array_sha256(arrays[name]) != expected:
            raise MultiscalePreflightError(f"seed array hash mismatch: {name}")
    if arrays["omega2_Mpl2"].shape != (4, 128):
        raise MultiscalePreflightError("4x128 seed modes required")
    return report, arrays, observed


def _diagnostic_rhs(
    n_relative: float,
    state: np.ndarray,
    N_seed: float,
    quantum_initial: np.ndarray,
    pilot: Planck2018Pilot,
    chi: ChiParameters,
) -> np.ndarray:
    sigma, sigma_dot, theta, theta_dot, H = map(float, state)
    if not np.all(np.isfinite(state)) or H <= 0.0:
        raise MultiscalePreflightError("diagnostic envelope left finite positive-H branch")
    classical = classical_terms(state)
    if classical["PX"] <= 0.0 or classical["K"] <= 0.0:
        raise MultiscalePreflightError("diagnostic envelope left hyperbolic branch")
    rho_standard, p_standard = standard_stress(
        np.array([N_seed + n_relative]), pilot
    )
    _mass2, dm_sigma, dm_theta = portal_terms(sigma, theta, chi)
    rho_q, pressure_q, chi2_q = map(float, quantum_initial)
    sigma_ddot = (
        -3.0 * H * sigma_dot
        + classical["P_sigma"]
        - 0.5 * float(dm_sigma) * chi2_q
    )
    theta_ddot = (
        classical["P_theta"]
        - 0.5 * float(dm_theta) * chi2_q
        - 3.0 * H * classical["PX"] * theta_dot
        - classical["PX_sigma"] * sigma_dot * theta_dot
    ) / classical["K"]
    Hdot = -0.5 * (
        classical["rho"]
        + classical["pressure"]
        + rho_q
        + pressure_q
        + float(rho_standard[0] + p_standard[0])
    )
    return np.array(
        [
            sigma_dot / H,
            sigma_ddot / H,
            theta_dot / H,
            theta_ddot / H,
            Hdot / H,
        ]
    )


def diagnostic_envelope(
    arrays: dict[str, np.ndarray],
    config: DiagnosticConfig | None = None,
) -> dict:
    config = DiagnosticConfig() if config is None else config
    config.validate()
    state6 = np.asarray(arrays["m1_state_physical_N"], dtype=float)
    if state6.shape != (6,) or state6[5] >= 0.0:
        raise MultiscalePreflightError("negative physical-N junction required")
    state0 = state6[:5].copy()
    N_seed = float(state6[5])
    span = -N_seed
    grid = np.linspace(0.0, span, config.nodes)
    quantum_initial = np.asarray(arrays["quantum_rho_pressure_chi2"], dtype=float)
    pilot = Planck2018Pilot()
    chi = ChiParameters()
    solution = solve_ivp(
        lambda n, y: _diagnostic_rhs(
            n, y, N_seed, quantum_initial, pilot, chi
        ),
        (0.0, span),
        state0,
        t_eval=grid,
        method="DOP853",
        rtol=config.rtol,
        atol=np.array([2.0e-12, 2.0e-16, 2.0e-12, 2.0e-16, 2.0e-16]),
        max_step=config.max_step_N,
    )
    if not solution.success or solution.y.shape != (5, config.nodes):
        raise MultiscalePreflightError(
            f"diagnostic envelope failed: {solution.message}"
        )
    rows = solution.y.T
    Hdot = np.empty(config.nodes)
    min_PX = np.inf
    min_K = np.inf
    for index, (n_relative, row) in enumerate(zip(grid, rows)):
        derivative = _diagnostic_rhs(
            float(n_relative), row, N_seed, quantum_initial, pilot, chi
        )
        Hdot[index] = derivative[4] * row[4]
        local = classical_terms(row)
        min_PX = min(min_PX, local["PX"])
        min_K = min(min_K, local["K"])

    mass2 = np.asarray(portal_terms(rows[:, 0], rows[:, 2], chi)[0])
    omega2_zero = mass2 - 2.25 * rows[:, 4] ** 2 - 1.5 * Hdot
    tachyonic = omega2_zero < 0.0
    if not np.any(tachyonic):
        raise MultiscalePreflightError(
            "diagnostic envelope did not expose the known tachyonic scaling problem"
        )
    growth_integrand = np.where(
        tachyonic,
        np.sqrt(np.maximum(-omega2_zero, 0.0)) / rows[:, 4],
        0.0,
    )
    growth_exponent = float(np.trapezoid(growth_integrand, grid))
    trajectory = ChiTrajectory(
        N=grid,
        H=rows[:, 4],
        Hdot=Hdot,
        sigma=rows[:, 0],
        theta=rows[:, 2],
    )
    fixed_split_rejected = False
    rejection = None
    try:
        curvature_uv_tail_n(trajectory, chi, nodes_per_octave=4)
    except TrajectoryError as exc:
        fixed_split_rejected = True
        rejection = str(exc)

    target_H = derived_reference(pilot)["H0_Mpl"]
    omega_over_H = np.sqrt(np.asarray(arrays["omega2_Mpl2"])) / state0[4]
    first_tachyonic_index = int(np.flatnonzero(tachyonic)[0])
    return {
        "role": "fixed_initial_quantum_triplet_architecture_diagnostic_not_a_background_candidate",
        "config": asdict(config),
        "N_seed_physical": N_seed,
        "N_span_to_present": span,
        "present_anchor_reached_as_grid_endpoint_only": bool(N_seed + grid[-1] == 0.0),
        "scale_factor_dynamic_range": float(np.exp(span)),
        "physical_split_transport_octaves": float(span / np.log(2.0)),
        "initial_omega_over_H_min": float(np.min(omega_over_H)),
        "initial_omega_over_H_max": float(np.max(omega_over_H)),
        "initial_shortest_phase_period_DeltaN": float(
            2.0 * np.pi / np.max(omega_over_H)
        ),
        "first_sampled_tachyonic_N_relative": float(grid[first_tachyonic_index]),
        "first_sampled_tachyonic_N_physical": float(
            N_seed + grid[first_tachyonic_index]
        ),
        "tachyonic_zero_mode_growth_exponent": growth_exponent,
        "float64_log_max": float(np.log(np.finfo(np.float64).max)),
        "diagnostic_endpoint_H_Mpl": float(rows[-1, 4]),
        "diagnostic_endpoint_H_over_shared_H0": float(rows[-1, 4] / target_H),
        "diagnostic_min_PX": float(min_PX),
        "diagnostic_min_K": float(min_K),
        "legacy_fixed_comoving_tail_split_rejected": fixed_split_rejected,
        "legacy_tail_rejection": rejection,
        "trajectory_rows_persisted": 0,
    }


def build_report(
    apeiron_root: Path,
    code_path: Path,
    config: DiagnosticConfig | None = None,
) -> dict:
    seed_report, arrays, authorities = load_seed(apeiron_root)
    diagnostic = diagnostic_envelope(arrays, config)
    required_architecture = {
        "transport_seed_modes_without_late_vacuum_reset": True,
        "moving_physical_split_with_adiabatic_state_transport": True,
        "log_amplitude_or_equivalent_tachyonic_rescaling": True,
        "signed_PV_cancellation_under_explicit_precision_control": True,
        "coupled_background_mode_backreaction_iteration": True,
        "independent_Ward_and_Wronskian_audits": True,
        "nested_time_momentum_tail_and_rounding_pilots_before_tolerance_freeze": True,
    }
    gates = {
        "chronology_safe_128_seed_hash_bound": True,
        "all_128_seed_arrays_hash_exact": True,
        "present_anchor_span_finite_and_positive": bool(
            np.isfinite(diagnostic["N_span_to_present"])
            and diagnostic["N_span_to_present"] > 0.0
        ),
        "initial_frequency_scale_resolved": bool(
            diagnostic["initial_omega_over_H_min"] > 0.0
        ),
        "diagnostic_exposes_float64_tachyonic_overflow_risk": bool(
            diagnostic["tachyonic_zero_mode_growth_exponent"]
            > diagnostic["float64_log_max"]
        ),
        "fixed_comoving_tail_split_fails_closed_on_diagnostic_envelope": bool(
            diagnostic["legacy_fixed_comoving_tail_split_rejected"]
        ),
        "required_multiscale_architecture_fully_registered": bool(
            all(required_architecture.values())
        ),
        "no_background_candidate_or_kernel_started": True,
    }
    passed = bool(all(gates.values()))
    return {
        "schema": "apeiron-ap1-m1-background-multiscale-preflight-v1.0",
        "updated_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "classification": (
            "M1_BACKGROUND_MULTISCALE_ARCHITECTURE_PREFLIGHT_PASS_NO_CANDIDATE"
            if passed
            else "M1_BACKGROUND_MULTISCALE_ARCHITECTURE_PREFLIGHT_NONPASS"
        ),
        "authority_sha256": {
            **authorities,
            "AP1/CODE/ap1_m1_background_multiscale_preflight.py": file_sha256(
                code_path
            ),
        },
        "seed_classification": seed_report["classification"],
        "diagnostic": diagnostic,
        "required_architecture": required_architecture,
        "gates": gates,
        "all_preflight_gates_pass": passed,
        "diagnostic_envelope_integrations": 1,
        "new_AP1_M1_background_runs": 0,
        "background_candidate_assessed": False,
        "background_tolerances_frozen": False,
        "physical_response_kernel_started": False,
        "seed_released": False,
        "checkpoint_eligible": passed,
        "nonpass_stored_or_used_as_seed": False,
        "equations_changed": False,
        "physics_changed": False,
        "parameters_changed": False,
        "existing_gate_thresholds_changed": False,
        "claim_boundary": (
            "multiscale solver architecture preflight only; the fixed-source "
            "diagnostic envelope is not a physical background, candidate, curve, "
            "fit, response kernel, or significance"
        ),
        "next_required": (
            "implement an independent moving-split/log-amplitude mode-transport "
            "reference path and validate it against the existing Decimal solver "
            "on overlapping short tachyonic windows before any long background pilot"
        ),
    }


def main() -> None:
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("apeiron_root", type=Path)
    parser.add_argument("code", type=Path)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    report = build_report(args.apeiron_root, args.code)
    if not report["all_preflight_gates_pass"]:
        raise MultiscalePreflightError("NONPASS architecture report not written")
    rendered = json.dumps(report, indent=2) + "\n"
    if args.output:
        args.output.write_text(rendered, encoding="utf-8")
    else:
        print(rendered, end="")


if __name__ == "__main__":
    main()
