from __future__ import annotations

from copy import deepcopy
import os
from pathlib import Path
import tempfile
import unittest

from ap1_r3_cm_counterterm_anchor import (
    CountertermAnchorError,
    EXPECTED_CANONICAL_SOURCE_HASHES,
    audit_runtime_sources,
    build_anchor_report,
    counterterm_shifts,
    portal_mass_derivatives,
    validate_anchor,
)
from chi_background_closure import ChiParameters, frozen_renormalization_constants


ROOT = Path(os.environ["APEIRON_AP1_ROOT"])
SOURCE_DIR = Path(os.environ["APEIRON_CANONICAL_SOURCE_DIR"])
SPEC = ROOT / "AP1" / "APEIRON_AP1_RESPONSE_KERNEL_SPEC_LATEST.md"
A3 = ROOT / "THEORY" / "APEIRON_PHASE_A3_SEMICLASSICAL_RESPONSE_REQUIREMENTS_LATEST.md"
CANONICAL = ROOT / "PUBLICATION" / "APEIRON_CANONICAL_MODEL_SPECIFICATION_LATEST.md"
MANIFEST = ROOT / "AP1" / "APEIRON_AP1_R3_LOCAL_VARIATION_MANIFEST_20260902T160055Z.json"


class AP1R3CmCountertermAnchorTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.report = build_anchor_report(SOURCE_DIR, SPEC, A3, CANONICAL, MANIFEST)

    def test_exact_canonical_hashes_are_required(self):
        self.assertEqual(audit_runtime_sources(SOURCE_DIR), EXPECTED_CANONICAL_SOURCE_HASHES)

    def test_runtime_marker_or_hash_tampering_fails_closed(self):
        with tempfile.TemporaryDirectory() as directory:
            target = Path(directory)
            for name in EXPECTED_CANONICAL_SOURCE_HASHES:
                (target / name).write_bytes((SOURCE_DIR / name).read_bytes())
            path = target / "frw_pv_covariant_uv_complete_v6_22.py"
            path.write_text(path.read_text(encoding="utf-8").replace("mr=-.5*C*m2", "mr=+.5*C*m2"), encoding="utf-8")
            with self.assertRaises(CountertermAnchorError):
                audit_runtime_sources(target)

    def test_action_coefficient_is_derived_from_frozen_C_chi2(self):
        coefficient = self.report["frozen_coefficient"]
        self.assertEqual(
            coefficient["coefficient_of_m_chi_squared_in_action"],
            0.5 * coefficient["C_chi2"],
        )
        self.assertFalse(self.report["exact_action_anchor"]["free_parameter_added"])

    def test_reference_triplet_reproduces_runtime_signs(self):
        reference = self.report["reference_slice_reproduction"]
        self.assertTrue(reference["runtime_rho_shift_match"])
        self.assertTrue(reference["runtime_pressure_shift_match"])
        self.assertTrue(reference["runtime_chi2_shift_match"])
        self.assertEqual(reference["rho_plus_pressure"], 0.0)

    def test_field_source_shift_is_minus_potential_gradient(self):
        p = ChiParameters()
        C = frozen_renormalization_constants(p)["C_chi2"]
        sigma, theta = 0.02, -0.61
        d = portal_mass_derivatives(sigma, theta, p)
        shifts = counterterm_shifts(sigma, theta, C, p)
        self.assertEqual(shifts["sigma_equation_rhs"], 0.5 * C * d["d_sigma"])
        self.assertEqual(shifts["theta_equation_rhs"], 0.5 * C * d["d_theta"])

    def test_portal_Hessian_is_symmetric(self):
        p = ChiParameters()
        C = frozen_renormalization_constants(p)["C_chi2"]
        shifts = counterterm_shifts(0.055, -0.76, C, p)
        self.assertEqual(shifts["sigma_theta_kernel"], shifts["theta_sigma_kernel"])

    def test_finite_difference_derivative_audit_passes(self):
        audit = self.report["derivative_audit"]
        self.assertLess(audit["max_first_derivative_relative_error"], 2.0e-8)
        self.assertLess(audit["max_pure_second_derivative_relative_error"], 2.0e-6)
        self.assertLess(audit["max_mixed_second_derivative_relative_error"], 2.0e-6)

    def test_all_nine_local_second_variation_slots_are_anchored(self):
        self.assertEqual(len(self.report["local_second_variations"]), 9)

    def test_anchor_keeps_physical_kernel_blocked(self):
        self.assertFalse(self.report["physical_kernel_ready"])
        self.assertNotIn("exact_S_ct,c_m_functional_anchor", self.report["remaining_blockers"])
        self.assertIn("new_AP1_M1_background", self.report["remaining_blockers"])

    def test_validation_rejects_a_new_free_parameter(self):
        candidate = deepcopy(self.report)
        candidate["exact_action_anchor"]["free_parameter_added"] = True
        with self.assertRaises(CountertermAnchorError):
            validate_anchor(candidate)

    def test_prior_manifest_without_hold_is_rejected(self):
        with tempfile.TemporaryDirectory() as directory:
            bad = Path(directory) / "manifest.json"
            text = MANIFEST.read_text(encoding="utf-8").replace(
                "DOCUMENTARY_HOLD_BLOCKS_PHYSICAL_KERNEL", "UNEXPECTED"
            )
            bad.write_text(text, encoding="utf-8")
            with self.assertRaises(CountertermAnchorError):
                build_anchor_report(SOURCE_DIR, SPEC, A3, CANONICAL, bad)


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