"""Tests for Theorem T: Coupling-Smoothness Theorem.

Theorem T states that for n >= 24, the coupling angle satisfies:

    θ(n) = arctan(√(seq5(n)² + seq6(n)²) / seq4(n))

where seq4 = rough part of L(n+1), seq5 = smooth part, seq6 = Mersenne-Lucas GCD.

This reveals the coupling angle as a geometric smoothness detector for Lucas numbers.
"""

import math

import numpy as np

from watkins_nn.kaleidoscope import (
    lucas,
    seq1_cycle_lb_numerator, seq3_mersenne_gcd,
    seq4_lucas_numerator, seq5_lucas_gcd, seq6_mersenne_lucas_gcd,
    seq7_binary_preservation, seq8_cross_gcd,
)
from watkins_nn.coupling import (
    coupling_angle,
    coupling_angle_asymptotic,
    coupling_smoothness,
    upper_triangle,
    lower_triangle,
)


# ── Theorem T: asymptotic formula ──────────────────────────────────────────────

def test_theorem_T_exact_for_n_ge_24():
    """θ(n) = arctan(√(s5²+s6²)/s4) to within 0.002° for n=24..200."""
    for n in range(24, 201):
        theta = coupling_angle(n)
        theta_T = coupling_angle_asymptotic(n)
        error_deg = abs(math.degrees(theta) - math.degrees(theta_T))
        assert error_deg < 0.002, \
            f"Theorem T error {error_deg:.6f}° > 0.002° at n={n}"


def test_theorem_T_converges():
    """Error between exact and asymptotic decreases for large n."""
    errors = []
    for n in [30, 60, 100, 150]:
        theta = coupling_angle(n)
        theta_T = coupling_angle_asymptotic(n)
        errors.append(abs(theta - theta_T))
    # Each should be smaller than the previous (general trend)
    assert errors[-1] < errors[0], \
        f"Error not decreasing: {errors[0]:.2e} → {errors[-1]:.2e}"


def test_asymptotic_formula_components():
    """The formula uses seq4, seq5, seq6 correctly."""
    for n in [30, 44, 50, 100]:
        s4 = seq4_lucas_numerator(n)
        s5 = seq5_lucas_gcd(n)
        s6 = seq6_mersenne_lucas_gcd(n)
        expected = math.atan2(math.sqrt(s5**2 + s6**2), s4)
        assert coupling_angle_asymptotic(n) == expected, \
            f"Formula mismatch at n={n}"


# ── Smoothness decomposition ──────────────────────────────────────────────────

def test_smoothness_decomposition_products():
    """seq4(n) * seq5(n) = L(n+1) for n=0..100."""
    for n in range(101):
        s4 = seq4_lucas_numerator(n)
        s5 = seq5_lucas_gcd(n)
        assert s4 * s5 == lucas(n + 1), \
            f"smooth × rough ≠ L({n+1}) at n={n}"


def test_smoothness_dict_keys():
    """coupling_smoothness returns all expected keys."""
    result = coupling_smoothness(30)
    expected_keys = {'smooth', 'rough', 'ml_gcd', 'ratio',
                     'theta_exact', 'theta_asymptotic', 'error'}
    assert set(result.keys()) == expected_keys


def test_smoothness_ratio_matches():
    """ratio = smooth/rough for n=0..100."""
    for n in range(101):
        result = coupling_smoothness(n)
        if result['rough'] > 0:
            expected = result['smooth'] / result['rough']
            assert abs(result['ratio'] - expected) < 1e-12, \
                f"ratio mismatch at n={n}"


def test_smoothness_error_small_for_asymptotic():
    """Error field < 0.002° for n >= 24."""
    for n in range(24, 151):
        result = coupling_smoothness(n)
        assert result['error'] < 0.002, \
            f"Smoothness error {result['error']:.6f}° at n={n}"


# ── n=14 triple resonance uniqueness ──────────────────────────────────────────

def test_n14_seq8_spike():
    """seq8(14) = 31 = M_5 (Mersenne prime)."""
    assert seq8_cross_gcd(14) == 31


def test_n14_smoothness_ratio_gt_1():
    """At n=14, the smoothness ratio seq5/seq4 > 1 (smooth > rough)."""
    s4 = seq4_lucas_numerator(14)
    s5 = seq5_lucas_gcd(14)
    assert s5 > s4, f"Expected s5={s5} > s4={s4} at n=14"
    assert s4 == 31, f"seq4(14) = {s4}, expected 31"
    assert s5 == 44, f"seq5(14) = {s5}, expected 44"


def test_n14_unique_triple_resonance():
    """n=14 is the only n with seq8>1 AND θ>1° AND seq5/seq4 > 1."""
    for n in range(200):
        s8 = seq8_cross_gcd(n)
        if s8 > 1 and n != 14:
            theta_deg = math.degrees(coupling_angle(n))
            s4 = seq4_lucas_numerator(n)
            s5 = seq5_lucas_gcd(n)
            # No other n has all three conditions
            assert not (theta_deg > 1.0 and s5 > s4), \
                f"n={n} also has triple resonance: θ={theta_deg}°, s5/s4={s5/s4}"


# ── n=44 terminal spike ──────────────────────────────────────────────────────

def test_n44_terminal_spike():
    """n=44 is the last θ > 1° in the asymptotic regime (n >= 24)."""
    for n in range(45, 301):
        theta_deg = math.degrees(coupling_angle(n))
        assert theta_deg < 1.0, \
            f"θ({n}) = {theta_deg}° > 1° after terminal spike"


def test_n44_anomalous_smoothness():
    """n=44 has the largest smoothness ratio for n >= 24."""
    ratio_44 = coupling_smoothness(44)['ratio']
    for n in range(24, 301):
        if n == 44:
            continue
        ratio_n = coupling_smoothness(n)['ratio']
        assert ratio_n < ratio_44, \
            f"n={n} has ratio {ratio_n:.6f} >= n=44 ratio {ratio_44:.6f}"


def test_n44_L45_factors():
    """L(45) = 2537720636, with smooth factors 2² × 11 × 19 × 31."""
    L45 = lucas(45)
    assert L45 == 2537720636
    # Smooth part (factors <= 45)
    s5 = seq5_lucas_gcd(44)
    assert s5 == 4 * 11 * 19 * 31  # = 25916
    # Rough part (factors > 45)
    s4 = seq4_lucas_numerator(44)
    assert s4 * s5 == L45


# ── Powers-of-2 falsification ────────────────────────────────────────────────

def test_powers_of_2_no_resonance():
    """At n = 2^k - 2 for k >= 5, θ < 0.001° and seq8 = 1."""
    for k in range(5, 10):
        n = 2**k - 2
        theta_deg = math.degrees(coupling_angle(n))
        s8 = seq8_cross_gcd(n)
        assert theta_deg < 0.001, \
            f"n={n} (2^{k}-2): θ={theta_deg}° >= 0.001°"
        assert s8 == 1, \
            f"n={n} (2^{k}-2): seq8={s8} != 1"


# ── Spike hierarchy ──────────────────────────────────────────────────────────

def test_spike_hierarchy():
    """Top 2 spikes are n=5, n=14. n=44 is largest in asymptotic regime."""
    angles = [(math.degrees(coupling_angle(n)), n) for n in range(200)]
    top2 = sorted(angles, reverse=True)[:2]
    assert top2[0][1] == 5, f"Largest spike at n={top2[0][1]}, expected 5"
    assert top2[1][1] == 14, f"Second spike at n={top2[1][1]}, expected 14"
    # n=44 is the largest spike in the asymptotic regime (n >= 24)
    asymptotic = [(a, n) for a, n in angles if n >= 24]
    top_asymptotic = sorted(asymptotic, reverse=True)[0]
    assert top_asymptotic[1] == 44, \
        f"Largest asymptotic spike at n={top_asymptotic[1]}, expected 44"


def test_completely_smooth_at_n5():
    """At n=5, L(6)=18 is completely 6-smooth: seq4=1, seq5=18."""
    assert lucas(6) == 18
    assert seq4_lucas_numerator(5) == 1
    assert seq5_lucas_gcd(5) == 18
    # This makes the asymptotic angle arctan(18/1) ≈ 86.8°
    theta_T = math.degrees(coupling_angle_asymptotic(5))
    assert np.isclose(theta_T, math.degrees(math.atan(18)), atol=0.01)  # n=5 is transient regime
