from __future__ import annotations

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

import numpy as np

from ap1_r3_unequal_time_reference import (
    FOCK_VARIANTS,
    MOMENTUM_NODE_BUDGETS,
    SYNTHETIC_IDENTITY_MATRIX,
    UnequalTimeReferenceError,
    assemble_source_block,
    build_reference_report,
    direct_fock_retarded_chi2,
    momentum_quadrature_canary,
    oscillator_wronskian,
    retarded_mask,
    wick_retarded_chi2,
)


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"
PREFLIGHT = ROOT / "AP1" / "APEIRON_AP1_R3_RESPONSE_KERNEL_PREFLIGHT_20260902T154220Z.json"
MANIFEST = ROOT / "AP1" / "APEIRON_AP1_R3_LOCAL_VARIATION_MANIFEST_LATEST.json"


class AP1R3UnequalTimeReferenceTests(unittest.TestCase):
    def test_retarded_mask_rejects_nonmonotonic_grid(self):
        with self.assertRaises(ValueError):
            retarded_mask(np.array((0.0, 0.5, 0.4)))

    def test_direct_Fock_and_Wick_paths_agree(self):
        times = np.linspace(0.0, 2.0, 17)
        wick = wick_retarded_chi2(times, 1.7)
        for dimension in FOCK_VARIANTS:
            direct = direct_fock_retarded_chi2(times, 1.7, dimension)
            np.testing.assert_allclose(direct, wick, rtol=0.0, atol=2.5e-13)

    def test_direct_path_is_retarded_real_and_zero_at_equal_time(self):
        response = direct_fock_retarded_chi2(np.linspace(-1.0, 1.0, 17), 1.7, 8)
        self.assertLessEqual(float(np.max(np.abs(np.triu(response, 1)))), 2.5e-13)
        self.assertLessEqual(float(np.max(np.abs(np.imag(response)))), 2.5e-13)
        self.assertLessEqual(float(np.max(np.abs(np.diag(response)))), 2.5e-13)

    def test_source_operator_block_has_symmetric_source_indices(self):
        base = wick_retarded_chi2(np.linspace(0.0, 1.0, 9), 1.7)
        block = assemble_source_block(base)
        self.assertEqual(block.shape, (3, 3, 9, 9))
        np.testing.assert_array_equal(block, block.swapaxes(0, 1))

    def test_synthetic_coupled_source_identity_annihilates_full_block(self):
        block = assemble_source_block(wick_retarded_chi2(np.linspace(0.0, 1.0, 9), 1.7))
        residual = np.einsum("ca,abij->cbij", SYNTHETIC_IDENTITY_MATRIX, block)
        np.testing.assert_array_equal(residual, np.zeros_like(residual))

    def test_oscillator_Wronskian_is_preserved(self):
        wronskian = oscillator_wronskian(np.linspace(0.0, 20.0, 257), 1.7)
        np.testing.assert_allclose(wronskian, 1j, rtol=0.0, atol=2.5e-13)

    def test_momentum_canary_converges_at_registered_8_16_32_nodes(self):
        exact = np.arctan(4.0) / 4.0
        errors = [abs(momentum_quadrature_canary(nodes) - exact) for nodes in MOMENTUM_NODE_BUDGETS]
        self.assertLess(errors[1], errors[0])
        self.assertLess(errors[2], errors[1])
        self.assertLessEqual(errors[2], 1.0e-12)

    def test_full_report_passes_only_synthetic_gates(self):
        report = build_reference_report(SPEC, A3, G22, PREFLIGHT, MANIFEST)
        self.assertEqual(
            report["classification"],
            "R3_SYNTHETIC_UNEQUAL_TIME_REFERENCE_PASS_PHYSICAL_KERNEL_BLOCKED",
        )
        self.assertTrue(all(report["synthetic_gates"].values()))
        self.assertFalse(report["execution"]["physical_kernel_computed"])
        self.assertFalse(report["registered_budgets"]["physical_gate_thresholds_frozen"])

    def test_every_physical_gate_remains_explicitly_unexecuted(self):
        report = build_reference_report(SPEC, A3, G22, PREFLIGHT, MANIFEST)
        self.assertTrue(report["physical_gate_status"])
        for status in report["physical_gate_status"].values():
            self.assertTrue(status.startswith("NOT_EXECUTED__"))

    def test_nonpass_prerequisite_is_rejected(self):
        payload = json.loads(G22.read_text(encoding="utf-8"))
        payload["classification"] = "G22_ORANGE"
        with tempfile.TemporaryDirectory() as directory:
            broken = Path(directory) / "g22.json"
            broken.write_text(json.dumps(payload), encoding="utf-8")
            with self.assertRaises(UnequalTimeReferenceError):
                build_reference_report(SPEC, A3, broken, PREFLIGHT, MANIFEST)


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