"""Tests for Theorem S: Adjugate Spectral Bridge.

For 3×3 M with eigenvalues λ₁ > λ₂ > λ₃:
  adj(M) eigenvalues = {λ₂λ₃, λ₁λ₃, λ₁λ₂}
  det(adj(M)) = det(M)²
  tr(adj(M)) = p₂ (Theorem Q)

When det(M) < 0: exactly one eigenvalue is negative (Theorem G),
so adj(M) has exactly 2 negative eigenvalues.
"""

import numpy as np

from watkins_nn.tensor import KaleidoscopeTensor
from watkins_nn.charpoly import trace_adj, det_M

KT = KaleidoscopeTensor()


def test_adjugate_eigenvalues_are_pairwise_products():
    """adj(M) eigenvalues = {λ₂λ₃, λ₁λ₃, λ₁λ₂} for n=1..25."""
    for n in range(1, 26):
        eigs = KT.eigenvalues(n)
        l1, l2, l3 = eigs[0], eigs[1], eigs[2]

        expected = sorted([l2*l3, l1*l3, l1*l2], reverse=True)

        C = KT.cofactor_matrix(n)
        actual = sorted(np.linalg.eigvals(C.T).real, reverse=True)

        for i in range(3):
            assert abs(expected[i] - actual[i]) < max(0.1, abs(expected[i]) * 1e-5), \
                f"adj eigenvalue mismatch at n={n}, i={i}: {expected[i]} vs {actual[i]}"


def test_adjugate_trace_equals_p2():
    """tr(adj(M)) = p₂ = tr(adj) from Theorem Q for n=1..30."""
    for n in range(1, 31):
        C = KT.cofactor_matrix(n)
        tr_adj = np.trace(C.T)
        p2 = trace_adj(n)
        assert abs(tr_adj - p2) < max(1, abs(p2) * 1e-8), \
            f"tr(adj) mismatch at n={n}: {tr_adj} vs {p2}"


def test_adjugate_determinant_equals_det_squared():
    """det(adj(M)) = det(M)² for n=1..20 (float-safe range)."""
    for n in range(1, 21):
        C = KT.cofactor_matrix(n)
        det_adj = np.linalg.det(C.T)
        d = det_M(n)
        assert abs(det_adj - d**2) < max(1, abs(d**2) * 1e-4), \
            f"det(adj) ≠ det² at n={n}: {det_adj} vs {d**2}"


def test_negative_det_implies_two_negative_adj_eigenvalues():
    """When det(M) < 0, adj(M) has exactly 2 negative eigenvalues."""
    neg_positions = [2, 6, 10, 18, 22]
    for n in neg_positions:
        C = KT.cofactor_matrix(n)
        adj_eigs = np.linalg.eigvals(C.T).real
        neg_count = sum(1 for e in adj_eigs if e < -1e-6)
        assert neg_count == 2, \
            f"Expected 2 negative adj eigenvalues at n={n}, got {neg_count}: {adj_eigs}"


def test_positive_det_implies_all_positive_or_zero_adj():
    """When det(M) > 0, all adj(M) eigenvalues are non-negative."""
    for n in [1, 3, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 20, 25]:
        d = det_M(n)
        if d <= 0:
            continue
        C = KT.cofactor_matrix(n)
        adj_eigs = np.linalg.eigvals(C.T).real
        assert all(e > -1e-6 for e in adj_eigs), \
            f"Negative adj eigenvalue at n={n} despite det > 0: {adj_eigs}"
