"""Tests for the March 21, 2026 spectral theorems (N, O, P, G).

Theorems N-P establish structural identities for the characteristic
polynomial, permanent, and trace of M(n)². Theorem G (eigenvalue reality)
is proved via Theorem N + Gershgorin + direct discriminant verification.
"""

import numpy as np

from watkins_nn.kaleidoscope import (
    lucas,
    seq1_cycle_lb_numerator, seq2_mersenne_numerator, seq3_mersenne_gcd,
    seq4_lucas_numerator, seq5_lucas_gcd, seq6_mersenne_lucas_gcd,
    seq7_binary_preservation, seq8_cross_gcd,
    KaleidoscopeMatrix,
)

KM = KaleidoscopeMatrix()


def _mat(n):
    """M(n) as list-of-lists of Python ints."""
    m = KM.full_matrix(n)
    return [[int(m[i, j]) for j in range(3)] for i in range(3)]


def _det3(a):
    return (a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
          - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
          + a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]))


def _perm3(a):
    return (a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0]
          + a[0][2]*a[1][0]*a[2][1] + a[0][0]*a[1][2]*a[2][1]
          + a[0][1]*a[1][0]*a[2][2] + a[0][2]*a[1][1]*a[2][0])


def _coupling_energy(n):
    """E(n) = seq3·seq6 + seq1·seq4 + seq7·seq5."""
    return (seq3_mersenne_gcd(n) * seq6_mersenne_lucas_gcd(n)
          + seq1_cycle_lb_numerator(n) * seq4_lucas_numerator(n)
          + seq7_binary_preservation(n) * seq5_lucas_gcd(n))


# ── Theorem N: Spectral Gap Decomposition ────────────────────────────────────

def test_theorem_N_identity():
    """p₁² - 3p₂ = ½Σ(dᵢ-dⱼ)² + 3E for n=0..40."""
    for n in range(41):
        a = _mat(n)
        d0, d1, d2 = a[0][0], a[1][1], a[2][2]
        p1 = d0 + d1 + d2
        m00 = a[1][1]*a[2][2] - a[1][2]*a[2][1]
        m11 = a[0][0]*a[2][2] - a[0][2]*a[2][0]
        m22 = a[0][0]*a[1][1] - a[0][1]*a[1][0]
        p2 = m00 + m11 + m22

        diag_sep = ((d0-d1)**2 + (d0-d2)**2 + (d1-d2)**2) // 2
        E = _coupling_energy(n)
        assert p1**2 - 3*p2 == diag_sep + 3*E, f"Thm N failed at n={n}"


def test_theorem_N_positivity():
    """q < 0 for all n, i.e., p₁² - 3p₂ > 0."""
    for n in range(41):
        a = _mat(n)
        d0, d1, d2 = a[0][0], a[1][1], a[2][2]
        p1 = d0 + d1 + d2
        m00 = a[1][1]*a[2][2] - a[1][2]*a[2][1]
        m11 = a[0][0]*a[2][2] - a[0][2]*a[2][0]
        m22 = a[0][0]*a[1][1] - a[0][1]*a[1][0]
        p2 = m00 + m11 + m22
        assert p1**2 - 3*p2 > 0, f"q ≥ 0 at n={n}"


def test_coupling_energy_positive():
    """E(n) ≥ 1 for all n ≥ 0, since seq1·seq4 ≥ 1."""
    for n in range(50):
        assert _coupling_energy(n) >= 1, f"E({n}) < 1"


# ── Theorem G: Eigenvalue Reality ────────────────────────────────────────────

def test_theorem_G_discriminant_positive():
    """Δ(n) > 0 for n=1..40 — three distinct real eigenvalues."""
    for n in range(1, 41):
        a = _mat(n)
        p1 = a[0][0] + a[1][1] + a[2][2]
        m00 = a[1][1]*a[2][2] - a[1][2]*a[2][1]
        m11 = a[0][0]*a[2][2] - a[0][2]*a[2][0]
        m22 = a[0][0]*a[1][1] - a[0][1]*a[1][0]
        p2 = m00 + m11 + m22
        p3 = _det3(a)
        delta = 18*p1*p2*p3 - 4*p2**3 + p1**2*p2**2 - 4*p1**3*p3 - 27*p3**2
        assert delta > 0, f"Δ({n}) = {delta} ≤ 0"


def test_theorem_G_eigenvalues_real():
    """All eigenvalues of M(n) are real for n=1..25."""
    for n in range(1, 26):
        mat = KM.full_matrix(n).astype(float)
        evals = np.linalg.eigvals(mat)
        assert all(abs(e.imag) < 1e-6 for e in evals), \
            f"Complex eigenvalue at n={n}: {evals}"


def test_theorem_G_eigenvalues_distinct():
    """No repeated eigenvalues for n=1..25."""
    for n in range(1, 26):
        mat = KM.full_matrix(n).astype(float)
        evals = sorted(np.linalg.eigvals(mat).real, reverse=True)
        for i in range(2):
            assert abs(evals[i] - evals[i+1]) > 1e-6, \
                f"Repeated eigenvalue at n={n}: {evals}"


# ── Theorem O: Permanent-Determinant Splitting ───────────────────────────────

def test_theorem_O():
    """perm(M) - det(M) = 2·(s2·s7·s5 + s3·s6·L(2n) + s1·s8·s4)."""
    for n in range(1, 26):
        a = _mat(n)
        diff = _perm3(a) - _det3(a)

        s1 = seq1_cycle_lb_numerator(n)
        s2 = seq2_mersenne_numerator(n)
        s3 = seq3_mersenne_gcd(n)
        s4 = seq4_lucas_numerator(n)
        s5 = seq5_lucas_gcd(n)
        s6 = seq6_mersenne_lucas_gcd(n)
        s7 = seq7_binary_preservation(n)
        s8 = seq8_cross_gcd(n)
        l2n = lucas(2 * n)

        anti_sarrus = s2*s7*s5 + s3*s6*l2n + s1*s8*s4
        assert diff == 2 * anti_sarrus, f"Thm O failed at n={n}"


# ── Theorem P: Trace-Squared Identity ────────────────────────────────────────

def test_theorem_P():
    """tr(M(n)²) = L(4n) + 2 + seq2² + seq8² + 2E."""
    for n in range(1, 26):
        a = _mat(n)
        tr_sq = sum(a[i][j] * a[j][i] for i in range(3) for j in range(3))

        s2 = seq2_mersenne_numerator(n)
        s8 = seq8_cross_gcd(n)
        E = _coupling_energy(n)
        predicted = lucas(4 * n) + 2 + s2**2 + s8**2 + 2 * E
        assert tr_sq == predicted, f"Thm P failed at n={n}"


# ── Determinant sign pattern ─────────────────────────────────────────────────

def test_determinant_negative_positions():
    """det(M(n)) < 0 only at n ∈ {2, 6, 10, 18, 22} for n ≤ 40."""
    expected_neg = {2, 6, 10, 18, 22}
    for n in range(1, 41):
        a = _mat(n)
        d = _det3(a)
        if n in expected_neg:
            assert d < 0, f"Expected det(M({n})) < 0 but got {d}"
        else:
            assert d > 0, f"Expected det(M({n})) > 0 but got {d}"


def test_negative_det_all_mod4_eq2():
    """All negative-det positions are ≡ 2 mod 4."""
    for n in range(1, 61):
        a = _mat(n)
        if _det3(a) < 0:
            assert n % 4 == 2, f"det(M({n})) < 0 but n ≢ 2 mod 4"
