from __future__ import annotations

import json
import os
from pathlib import Path
import tempfile
import unittest

import numpy as np

from ap1_r3_response_kernel_preflight import (
    ResponsePreflightError,
    build_preflight,
    chi2_unit_spectral_canary,
    harmonic_wightman_canary,
    portal_mass_jet,
    retarded_mask,
)


ROOT = Path(os.environ["APEIRON_AP1_ROOT"])
SPEC = ROOT / "AP1" / "APEIRON_AP1_RESPONSE_KERNEL_SPEC_LATEST.md"
A3 = ROOT / "THEORY" / "APEIRON_PHASE_A3_SEMICLASSICAL_RESPONSE_REQUIREMENTS_LATEST.md"
G22 = ROOT / "AP1" / "APEIRON_AP1_R2C_G22_PASS_CHECKPOINT_20260902T151240Z.json"


class AP1R3ResponseKernelPreflightTests(unittest.TestCase):
    def test_retarded_mask_is_exactly_lower_triangular(self):
        mask = retarded_mask(np.linspace(-1.0, 1.0, 9))
        np.testing.assert_array_equal(mask, np.tril(np.ones((9, 9), dtype=bool)))

    def test_time_grid_fails_closed(self):
        with self.assertRaises(ValueError):
            retarded_mask(np.array([0.0, 0.5, 0.5]))

    def test_harmonic_wightman_canary_has_real_retarded_chi2_block(self):
        times = np.linspace(0.0, 3.0, 17)
        wightman = harmonic_wightman_canary(times)
        response = chi2_unit_spectral_canary(wightman, times)
        self.assertTrue(np.isrealobj(response))
        self.assertEqual(float(np.max(np.abs(np.triu(response, 1)))), 0.0)
        self.assertLess(float(np.max(np.abs(np.diag(response)))), 1.0e-15)

    def test_nonhermitian_wightman_fails_closed(self):
        times = np.linspace(0.0, 1.0, 5)
        wightman = harmonic_wightman_canary(times)
        wightman[0, 1] += 0.1j
        with self.assertRaises(ResponsePreflightError):
            chi2_unit_spectral_canary(wightman, times)

    def test_portal_gradient_matches_centered_finite_differences(self):
        sigma, theta = 0.013, -0.21
        epsilon = 1.0e-7
        jet = portal_mass_jet(sigma, theta)
        d_sigma = (
            portal_mass_jet(sigma + epsilon, theta)["mass_squared"]
            - portal_mass_jet(sigma - epsilon, theta)["mass_squared"]
        ) / (2.0 * epsilon)
        d_theta = (
            portal_mass_jet(sigma, theta + epsilon)["mass_squared"]
            - portal_mass_jet(sigma, theta - epsilon)["mass_squared"]
        ) / (2.0 * epsilon)
        np.testing.assert_allclose(jet["gradient"], [d_sigma, d_theta], rtol=2.0e-9, atol=1.0e-15)

    def test_portal_hessian_is_symmetric(self):
        hessian = np.asarray(portal_mass_jet(0.013, -0.21)["hessian"])
        np.testing.assert_array_equal(hessian, hessian.T)

    def test_preflight_passes_without_claiming_a_kernel(self):
        report = build_preflight(SPEC, A3, G22)
        self.assertEqual(
            report["classification"],
            "R3_STRUCTURAL_PREFLIGHT_PASS_PHYSICAL_KERNEL_NOT_COMPUTED",
        )
        self.assertFalse(report["execution"]["physical_kernel_computed"])
        self.assertFalse(report["execution"]["production_curve_released"])
        self.assertFalse(report["pilot_registry"]["physical_gate_thresholds_frozen"])
        self.assertFalse(report["pilot_registry"]["post_hoc_threshold_adjustment_allowed"])
        self.assertTrue(all(report["analytic_canaries"]["gates"].values()))

    def test_nonpass_g22_checkpoint_is_rejected(self):
        payload = json.loads(G22.read_text(encoding="utf-8"))
        payload["classification"] = "G22_ORANGE"
        with tempfile.TemporaryDirectory() as directory:
            candidate = Path(directory) / "g22.json"
            candidate.write_text(json.dumps(payload), encoding="utf-8")
            with self.assertRaises(ResponsePreflightError):
                build_preflight(SPEC, A3, candidate)


if __name__ == "__main__":
    unittest.main()
