"""
Tests for VX Core — constants, geometry, and state classification.

These tests verify the mathematical identities that form the
foundation of the VirelaiX framework.
"""

import math
import pytest
import numpy as np

from virelaix.core.constants import PHI, T_STAR, LAMBDA_STAR, TAU_STAR, LAMBDA_EQ, KAPPA_EQ, ETA_EQ
from virelaix.core.geometry import (
    conservation_check,
    softmax_normalize,
    softmax_tau,
    generating_functional,
    gradient_F,
    hessian_F,
    distance_to_equilibrium,
    rebalance_step,
    fugacity_Z,
    max_lambda_at_temperature,
    critical_temperature_for_threshold,
    project_to_simplex,
)
from virelaix.core.states import classify_state, VXState


# ── Constants ────────────────────────────────────────────────

class TestConstants:
    """Verify fundamental identities."""

    def test_golden_ratio(self):
        assert abs(PHI - (1 + math.sqrt(5)) / 2) < 1e-15

    def test_golden_ratio_property(self):
        """φ + 1 = φ² — the defining property."""
        assert abs(PHI + 1 - PHI ** 2) < 1e-14

    def test_lambda_star(self):
        assert abs(LAMBDA_STAR - 1 / PHI) < 1e-15

    def test_T_star(self):
        assert abs(T_STAR - PHI / math.log(2 * PHI)) < 1e-15

    def test_tau_star(self):
        assert abs(TAU_STAR - 1 / math.log(2 * PHI)) < 1e-15

    def test_phi_chain(self):
        """τ* × φ = T*"""
        assert abs(TAU_STAR * PHI - T_STAR) < 1e-14

    def test_exponential_identity(self):
        """e^(1/τ*) = 2φ"""
        assert abs(math.exp(1 / TAU_STAR) - 2 * PHI) < 1e-12

    def test_equilibrium_conservation(self):
        assert abs(LAMBDA_EQ + KAPPA_EQ + ETA_EQ - 1.0) < 1e-15


# ── Geometry ─────────────────────────────────────────────────

class TestGeometry:
    """Test the geometry engine."""

    def test_conservation_check_pass(self):
        dev = conservation_check(0.5, 0.3, 0.2)
        assert dev < 1e-15

    def test_conservation_check_fail(self):
        with pytest.raises(ValueError):
            conservation_check(0.5, 0.3, 0.3)  # sums to 1.1

    def test_softmax_conservation(self):
        """Softmax output always sums to 1."""
        raw = np.array([0.8, 0.3, 0.1])
        lam, kap, eta = softmax_normalize(raw)
        assert abs(lam + kap + eta - 1.0) < 1e-14

    def test_softmax_ordering(self):
        """Softmax preserves ordering."""
        raw = np.array([0.9, 0.5, 0.1])
        lam, kap, eta = softmax_normalize(raw)
        assert lam > kap > eta

    def test_softmax_tau_conservation(self):
        raw = np.array([1.0, 0.0, 0.0])
        lam, kap, eta = softmax_tau(raw, TAU_STAR)
        assert abs(lam + kap + eta - 1.0) < 1e-14

    def test_tau_star_theorem(self):
        """THE key theorem: softmax_{τ*}(1,0,0) = 1/φ."""
        raw = np.array([1.0, 0.0, 0.0])
        lam, kap, eta = softmax_tau(raw, TAU_STAR)
        assert abs(lam - LAMBDA_STAR) < 1e-10, f"Expected {LAMBDA_STAR}, got {lam}"

    def test_subcritical_crosses_threshold(self):
        """Below τ*, max coherence exceeds threshold."""
        raw = np.array([1.0, 0.0, 0.0])
        lam, _, _ = softmax_tau(raw, 0.7)
        assert lam > LAMBDA_STAR

    def test_supercritical_below_threshold(self):
        """Above τ*, max coherence cannot reach threshold."""
        raw = np.array([1.0, 0.0, 0.0])
        lam, _, _ = softmax_tau(raw, 1.0)
        assert lam < LAMBDA_STAR

    def test_F_at_equilibrium(self):
        """F at VX equilibrium should be a minimum."""
        F_eq = generating_functional(LAMBDA_EQ, KAPPA_EQ, ETA_EQ)
        # Perturb and check F increases
        for delta in [0.05, -0.05]:
            lam_p = LAMBDA_EQ + delta
            kap_p = KAPPA_EQ - delta / 2
            eta_p = ETA_EQ - delta / 2
            F_p = generating_functional(lam_p, kap_p, eta_p)
            assert F_p > F_eq, f"F should increase away from equilibrium"

    def test_gradient_zero_at_equilibrium(self):
        """∇F = 0 at VX equilibrium."""
        grad = gradient_F(LAMBDA_EQ, KAPPA_EQ, ETA_EQ)
        assert np.linalg.norm(grad) < 1e-10

    def test_hessian_positive_at_equilibrium(self):
        """Hessian is positive definite at equilibrium (confirming minimum)."""
        H = hessian_F(LAMBDA_EQ, KAPPA_EQ, ETA_EQ)
        eigenvalues = np.linalg.eigvalsh(H)
        assert all(ev > 0 for ev in eigenvalues)

    def test_distance_to_equilibrium_zero(self):
        dist = distance_to_equilibrium(LAMBDA_EQ, KAPPA_EQ, ETA_EQ)
        assert dist < 1e-15

    def test_distance_to_equilibrium_positive(self):
        dist = distance_to_equilibrium(0.5, 0.3, 0.2)
        assert dist > 0

    def test_rebalance_moves_toward_equilibrium(self):
        """Rebalancing should decrease distance to equilibrium."""
        l, k, e = 0.5, 0.3, 0.2
        d_before = distance_to_equilibrium(l, k, e)
        new_l, new_k, new_e = rebalance_step(l, k, e)
        d_after = distance_to_equilibrium(new_l, new_k, new_e)
        assert d_after < d_before

    def test_fugacity_at_threshold(self):
        """Z(λ*) = 2φ."""
        Z = fugacity_Z(LAMBDA_STAR)
        assert abs(Z - 2 * PHI) < 1e-8

    def test_max_lambda_at_tau_star(self):
        lam_max = max_lambda_at_temperature(TAU_STAR)
        assert abs(lam_max - LAMBDA_STAR) < 1e-10

    def test_critical_temperature_inversion(self):
        """critical_temperature_for_threshold(1/φ) = τ*."""
        tau = critical_temperature_for_threshold(LAMBDA_STAR)
        assert abs(tau - TAU_STAR) < 1e-10

    def test_project_to_simplex(self):
        result = project_to_simplex(np.array([3.0, 1.0, 1.0]))
        assert abs(sum(result) - 1.0) < 1e-15
        assert result[0] > result[1]


# ── States ───────────────────────────────────────────────────

class TestStates:
    def test_coherent(self):
        assert classify_state(0.7) == VXState.COHERENT

    def test_transitional(self):
        assert classify_state(0.5) == VXState.TRANSITIONAL

    def test_stressed(self):
        assert classify_state(0.3) == VXState.STRESSED

    def test_turbulent(self):
        assert classify_state(0.1) == VXState.TURBULENT

    def test_threshold_boundary(self):
        """At exactly λ*, state should be TRANSITIONAL (not strictly above)."""
        assert classify_state(LAMBDA_STAR) == VXState.TRANSITIONAL

    def test_above_threshold(self):
        assert classify_state(LAMBDA_STAR + 0.001) == VXState.COHERENT
