"""Tests for Result 27: Determinant Factorization.

det(M(n)) → seq8(n) · seq2(n) · L(2n) with exponentially decreasing error.

The determinant of the Kaleidoscope Matrix asymptotically factorizes into
the product of the three diagonal entries' "characteristic" values:
  bridge (seq8) × Mersenne (seq2) × Lucas (L(2n))

When seq8 = 1 (generic case), det → seq2 · L(2n) = Δ_ML + (correction terms).
When seq8 = p (prime spike), det → p · seq2 · L(2n).
"""

from watkins_nn.charpoly import det_M
from watkins_nn.kaleidoscope import (
    seq2_mersenne_numerator, seq8_cross_gcd, seq9_weight_moments,
)


def test_det_approaches_s8_s2_l2n():
    """det(M(n)) / (seq8·seq2·L(2n)) → 1 for n where det > 0 and seq8 = 1."""
    for n in [20, 21, 23, 24, 25, 29, 31, 33, 37, 41, 43, 47]:
        d = det_M(n)
        s2 = seq2_mersenne_numerator(n)
        s8 = seq8_cross_gcd(n)
        l2n = seq9_weight_moments(n)
        if s8 == 1 and d > 0 and s2 * l2n > 0:
            ratio = d / (s2 * l2n)
            assert abs(1 - ratio) < 0.01, \
                f"det ratio not converging at n={n}: {ratio}"


def test_det_ratio_at_seq8_spike():
    """When seq8 = p, det ≈ p · seq2 · L(2n)."""
    # n=14: seq8 = 31
    d14 = det_M(14)
    s2_14 = seq2_mersenne_numerator(14)
    l2n_14 = seq9_weight_moments(14)
    expected = 31 * s2_14 * l2n_14
    assert abs(d14 / expected - 1) < 0.001, \
        f"n=14: det/(31·s2·L2n) = {d14/expected}"


def test_det_negative_positions_exact():
    """det < 0 only at n ∈ {2, 6, 10, 18, 22} through n=60."""
    neg = {2, 6, 10, 18, 22}
    for n in range(1, 61):
        d = det_M(n)
        if n in neg:
            assert d < 0, f"Expected det(M({n})) < 0"
        else:
            assert d > 0, f"Expected det(M({n})) > 0 but got {d}"


def test_det_convergence_rate():
    """Error in det/(s2·L2n) decreases exponentially for seq8=1 positions."""
    errors = []
    for n in range(30, 51):
        s8 = seq8_cross_gcd(n)
        d = det_M(n)
        s2 = seq2_mersenne_numerator(n)
        l2n = seq9_weight_moments(n)
        if s8 == 1 and d > 0 and s2 * l2n > 0:
            err = abs(1 - d / (s2 * l2n))
            errors.append(err)
    # At least some errors should be < 1e-6
    assert min(errors) < 1e-6, f"Best error = {min(errors)}, expected < 1e-6"
