"""Tests for Theorems V (Hyperboloid) and W (Determinant Factorization).

Theorem V: Five points on cosh²t - sinh²t = 1 form a rank-2 lattice
generated by ln(φ) and ln(2). Coupling point = Mersenne + Fibonacci.

Theorem W: det(M(n)) = 4·seq8(n)·(2φ²)^n / seq3(n) + O((2ψ²)^n).
Corollary: det(M(n)) > 0 for all n ≥ 23.
"""

import math

import numpy as np

from watkins_nn.constants import PHI
from watkins_nn.kaleidoscope import lucas, fibonacci, mersenne, seq3_mersenne_gcd, seq8_cross_gcd
from watkins_nn.charpoly import det_M, det_M_predicted
from watkins_nn.coupling import (
    HYPERBOLOID_POINTS,
    hyperboloid_point,
    hyperboloid_all,
    hyperboloid_pythagorean,
    lattice_density_check,
)

SQRT5 = math.sqrt(5)
TWO_PHI_SQ = 2 * PHI**2


# ── Theorem V: Hyperboloid lattice ────────────────────────────────────────────

def test_five_canonical_points():
    """All five canonical points satisfy cosh²t - sinh²t = 1."""
    pts = hyperboloid_all()
    assert len(pts) == 5
    for name, data in pts.items():
        assert abs(data["verification"] - 1.0) < 1e-12, \
            f"{name}: cosh²-sinh² = {data['verification']}"


def test_fibonacci_point():
    """Fibonacci point: (cosh, sinh) = (√5/2, 1/2)."""
    p = hyperboloid_point("fibonacci")
    assert abs(p["cosh_t"] - SQRT5 / 2) < 1e-12
    assert abs(p["sinh_t"] - 0.5) < 1e-12


def test_mersenne_point():
    """Mersenne point: (cosh, sinh) = (5/4, 3/4)."""
    p = hyperboloid_point("mersenne")
    assert abs(p["cosh_t"] - 1.25) < 1e-12
    assert abs(p["sinh_t"] - 0.75) < 1e-12


def test_lucas_point():
    """Lucas point: (cosh, sinh) = (3/2, √5/2)."""
    p = hyperboloid_point("lucas")
    assert abs(p["cosh_t"] - 1.5) < 1e-12
    assert abs(p["sinh_t"] - SQRT5 / 2) < 1e-12


def test_coupling_point_exact():
    """Coupling point: (cosh, sinh) = ((5√5+3)/8, (3√5+5)/8)."""
    p = hyperboloid_point("coupling")
    assert abs(p["cosh_t"] - (5 * SQRT5 + 3) / 8) < 1e-12
    assert abs(p["sinh_t"] - (3 * SQRT5 + 5) / 8) < 1e-12


def test_coupling_is_mersenne_plus_fibonacci():
    """t_coupling = t_mersenne + t_fibonacci (hyperbolic addition)."""
    pF = hyperboloid_point("fibonacci")
    pM = hyperboloid_point("mersenne")
    pC = hyperboloid_point("coupling")
    assert abs(pC["t"] - pF["t"] - pM["t"]) < 1e-12


def test_lucas_is_double_fibonacci():
    """t_lucas = 2 × t_fibonacci."""
    pF = hyperboloid_point("fibonacci")
    pL = hyperboloid_point("lucas")
    assert abs(pL["t"] - 2 * pF["t"]) < 1e-12


def test_spectral_is_mersenne_plus_lucas():
    """t_spectral = t_mersenne + t_lucas."""
    pM = hyperboloid_point("mersenne")
    pL = hyperboloid_point("lucas")
    pS = hyperboloid_point("spectral")
    assert abs(pS["t"] - pM["t"] - pL["t"]) < 1e-12


def test_harmonic_balance():
    """t_lucas + t_mersenne = t_fibonacci + t_coupling."""
    pts = hyperboloid_all()
    lhs = pts["lucas"]["t"] + pts["mersenne"]["t"]
    rhs = pts["fibonacci"]["t"] + pts["coupling"]["t"]
    assert abs(lhs - rhs) < 1e-12


def test_arbitrary_lattice_point():
    """An arbitrary (a,b) point still lies on the hyperboloid."""
    p = hyperboloid_point((3, 2))
    assert abs(p["verification"] - 1.0) < 1e-10


# ── Coupling Pythagorean identity ─────────────────────────────────────────────

def test_pythagorean_identity():
    """(5√5+3)² - (3√5+5)² = 64 = 8²."""
    result = hyperboloid_pythagorean()
    assert result["is_64"]
    assert abs(result["difference"] - 64) < 1e-10


def test_sqrt5_cancellation():
    """The √5 terms cancel algebraically: (134+30√5) - (70+30√5) = 64."""
    # (5√5+3)² = 75+30√5+9 = 134+30√5... wait, 25*5=125, not 75.
    # (5√5)² = 125, 2·5√5·3 = 30√5, 3²=9 → 134 + 30√5
    # (3√5)² = 45, 2·3√5·5 = 30√5, 5²=25 → 70 + 30√5
    # Difference: 134 - 70 = 64, √5 terms cancel.
    assert (134 + 30 * SQRT5) - (70 + 30 * SQRT5) == 64


# ── Lattice density ───────────────────────────────────────────────────────────

def test_lattice_density():
    """W² = log₂(φ) is irrational → fractional parts {a·W²} are dense."""
    fracs = lattice_density_check(100)
    assert len(fracs) == 100
    # Dense in [0,1]: check all 10 bins have points
    bins = [0] * 10
    for f in fracs:
        bins[int(f * 10)] += 1
    assert all(b > 0 for b in bins), f"Bins: {bins}"


def test_W_squared_value():
    """W² = log₂(φ) ≈ 0.6942."""
    W2 = math.log2(PHI)
    assert abs(W2 - 0.6942419136) < 1e-8


# ── Chebyshev at lattice points ───────────────────────────────────────────────

def test_chebyshev_fibonacci_gives_lucas():
    """cosh(k·ln φ) = (φ^k + φ^{-k})/2 relates to Lucas for even k."""
    tF = math.log(PHI)
    # For even k: φ^{-k} = ψ^k, so cosh(k·tF) = (φ^k+ψ^k)/2 = L(k)/2
    for k in range(2, 21, 2):
        Tk = math.cosh(k * tF)
        assert abs(Tk - lucas(k) / 2) < 1e-6, f"k={k}"


def test_chebyshev_lucas_gives_bisection():
    """T_k(3/2) = L(2k)/2 for k=1..20."""
    tL = 2 * math.log(PHI)
    for k in range(1, 21):
        Tk = math.cosh(k * tL)
        assert abs(Tk - lucas(2 * k) / 2) < 1e-6, f"k={k}"


def test_binet_mersenne_identity():
    """(2φ)^k = 2^{k-1} × (L(k) + √5·F(k)) for k=0..15."""
    TWO_PHI = 2 * PHI
    for k in range(16):
        lhs = TWO_PHI ** k
        rhs = 2 ** (k - 1) * (lucas(k) + SQRT5 * fibonacci(k))
        assert abs(lhs - rhs) < max(1, abs(lhs)) * 1e-10, f"k={k}"


# ── Theorem W: Determinant factorization ──────────────────────────────────────

def test_det_predicted_formula():
    """det_M_predicted(n) = 4·seq8·(2φ²)^n/seq3 for n=25..60."""
    for n in range(25, 61):
        s3 = seq3_mersenne_gcd(n)
        s8 = seq8_cross_gcd(n)
        expected = 4.0 * s8 * TWO_PHI_SQ ** n / s3
        assert abs(det_M_predicted(n) - expected) < max(1, abs(expected)) * 1e-12, \
            f"Formula mismatch at n={n}"


def test_det_predicted_matches_exact():
    """det_M_predicted ≈ det_M when seq3 is small (diagonal-dominated)."""
    # Theorem W is accurate when the diagonal product dominates.
    # This holds when seq3 (Mersenne smooth part) is small, meaning
    # M(n+2) has few small prime factors.
    for n in range(30, 80):
        s3 = seq3_mersenne_gcd(n)
        if s3 > 100 or seq8_cross_gcd(n) > 1:
            continue
        exact = det_M(n)
        pred = det_M_predicted(n)
        if abs(exact) < 100:
            continue
        rel = abs(pred - exact) / abs(exact)
        assert rel < 1e-4, f"Relative error {rel:.2e} at n={n}, seq3={s3}"


def test_mersenne_lucas_spectral_product():
    """M(n+2) × L(2n) / (4 × (2φ²)^n) → 1."""
    for n in [30, 40, 50, 60]:
        Mn2 = mersenne(n + 2)
        L2n = lucas(2 * n)
        ratio = Mn2 * L2n / (4 * TWO_PHI_SQ ** n)
        assert abs(ratio - 1.0) < 1e-8, f"n={n}: ratio = {ratio}"


def test_det_positive_for_n_ge_23():
    """det(M(n)) > 0 for all n ≥ 23 (determinant sign resolution)."""
    for n in range(23, 81):
        assert det_M(n) > 0, f"det(M({n})) = {det_M(n)} ≤ 0"


def test_det_negative_positions():
    """det(M(n)) < 0 only at n ∈ {2, 6, 10, 18, 22}."""
    neg_positions = {2, 6, 10, 18, 22}
    for n in range(100):
        d = det_M(n)
        if d < 0:
            assert n in neg_positions, \
                f"Unexpected negative det at n={n}: {d}"
