"""Tests for the polynomial_tower module.

Tests cover:
- Fibonacci and Lucas sequences (values, recurrence, edge cases)
- Conservation polynomial t^2 - 3t + 1 = 0 (roots, Vieta, discriminant)
- Polynomial tower (discriminant identity, root verification, classification)
- Hierarchy exponent 75/4
- Even/odd palindromic classification
- Discriminant tower Delta_n = 5*F(n)^2
- Icosahedral dihedral angle
- Pell equation x^2 - 15*y^2 = 1
- Class number h(Q(sqrt(15))) = 2
- Subfield data
- Full report
"""

import math
import pytest

from watkins_nn.constants import PHI, PHI_INV, PHI_SQUARED, SQRT5, CONSERVATION_EPSILON
from watkins_nn.polynomial_tower import (
    fibonacci,
    lucas,
    LUCAS_TABLE,
    FIBONACCI_TABLE,
    TowerLevel,
    conservation_polynomial,
    polynomial_tower,
    hierarchy_exponent_analysis,
    even_odd_classification,
    discriminant_tower,
    icosahedral_dihedral,
    pell_equation_15,
    class_number_Q_sqrt15,
    SUBFIELD_DATA,
    full_polynomial_tower_report,
)


# ── Fibonacci ────────────────────────────────────────────────────────────────

class TestFibonacci:

    def test_known_values(self):
        assert fibonacci(0) == 0
        assert fibonacci(1) == 1
        assert fibonacci(2) == 1
        assert fibonacci(5) == 5
        assert fibonacci(10) == 55
        assert fibonacci(15) == 610

    def test_recurrence(self):
        for n in range(2, 20):
            assert fibonacci(n) == fibonacci(n - 1) + fibonacci(n - 2)

    def test_negative_raises(self):
        with pytest.raises(ValueError):
            fibonacci(-1)

    def test_table_matches(self):
        for n in range(16):
            assert FIBONACCI_TABLE[n] == fibonacci(n)


# ── Lucas ────────────────────────────────────────────────────────────────────

class TestLucas:

    def test_known_values(self):
        assert lucas(0) == 2
        assert lucas(1) == 1
        assert lucas(2) == 3
        assert lucas(5) == 11
        assert lucas(10) == 123

    def test_recurrence(self):
        for n in range(2, 20):
            assert lucas(n) == lucas(n - 1) + lucas(n - 2)

    def test_negative_raises(self):
        with pytest.raises(ValueError):
            lucas(-1)

    def test_table_matches(self):
        for n in range(16):
            assert LUCAS_TABLE[n] == lucas(n)

    def test_lucas_equals_phi_power_trace(self):
        """L(n) = phi^n + phi_bar^n."""
        phi_bar = (1 - SQRT5) / 2
        for n in range(15):
            expected = PHI ** n + phi_bar ** n
            assert abs(expected - lucas(n)) < 1e-8


# ── Conservation Polynomial ──────────────────────────────────────────────────

class TestConservationPolynomial:

    def test_roots_satisfy_polynomial(self):
        r = conservation_polynomial()
        assert r['residuals']['both_zero']

    def test_vieta_sum(self):
        r = conservation_polynomial()
        assert r['vieta']['sum_equals_trace']

    def test_vieta_product(self):
        r = conservation_polynomial()
        assert r['vieta']['product_equals_norm']

    def test_discriminant_is_5(self):
        r = conservation_polynomial()
        assert r['discriminant']['equals_5']
        assert r['discriminant']['value'] == 5

    def test_trace_is_3(self):
        r = conservation_polynomial()
        assert r['vieta']['trace'] == 3

    def test_norm_is_1(self):
        r = conservation_polynomial()
        assert r['vieta']['norm'] == 1

    def test_bridge_number_is_15(self):
        r = conservation_polynomial()
        assert r['encoding']['bridge_15']['value'] == 15


# ── Polynomial Tower ────────────────────────────────────────────────────────

class TestPolynomialTower:

    def test_all_discriminants_are_5Fn2(self):
        r = polynomial_tower(n_max=15)
        assert r['all_discriminants_are_5Fn2']

    def test_all_roots_verified(self):
        # n_max=12 avoids floating-point limits at very high powers
        r = polynomial_tower(n_max=12)
        assert r['all_roots_verified']

    def test_level_count(self):
        r = polynomial_tower(n_max=10)
        assert len(r['levels']) == 11  # 0..10 inclusive

    def test_level_2_gives_conservation(self):
        """Level 2: t^2 - 3t + 1 = 0 (the conservation polynomial)."""
        r = polynomial_tower(n_max=5)
        lv2 = r['levels'][2]
        assert lv2.lucas_n == 3
        assert lv2.sign == 1
        assert lv2.parity == 'even'
        assert lv2.poly_class == 'palindromic'

    def test_even_palindromic_odd_anti(self):
        r = polynomial_tower(n_max=10)
        for lv in r['levels']:
            if lv.n % 2 == 0:
                assert lv.poly_class == 'palindromic'
                assert lv.sign == 1
            else:
                assert lv.poly_class == 'anti-palindromic'
                assert lv.sign == -1

    def test_discriminant_identity_explicit(self):
        """Delta_n = L(n)^2 - 4*(-1)^n = 5*F(n)^2 for several n."""
        for n in range(20):
            Ln = lucas(n)
            Fn = fibonacci(n)
            disc = Ln ** 2 - 4 * ((-1) ** n)
            assert disc == 5 * Fn ** 2, f"n={n}: {disc} != 5*{Fn}^2"


# ── Hierarchy Exponent ───────────────────────────────────────────────────────

class TestHierarchyExponent:

    def test_bridge_number_is_15(self):
        r = hierarchy_exponent_analysis()
        assert r['is_bridge_number']
        assert r['L6_minus_L2'] == 15

    def test_exponent_is_75_4(self):
        r = hierarchy_exponent_analysis()
        assert r['both_equal_75_4']
        assert abs(r['hierarchy_exponent'] - 75.0 / 4.0) < 1e-14

    def test_anchor_exponent_zero(self):
        r = hierarchy_exponent_analysis()
        assert abs(r['exponent_at_2']) < 1e-14


# ── Even/Odd Classification ─────────────────────────────────────────────────

class TestEvenOddClassification:

    def test_even_levels_palindromic(self):
        r = even_odd_classification(n_max=10)
        for lv in r['even_structure']['levels']:
            assert lv['poly_class'] == 'palindromic'

    def test_odd_levels_anti_palindromic(self):
        r = even_odd_classification(n_max=10)
        for lv in r['odd_dynamics']['levels']:
            assert lv['poly_class'] == 'anti-palindromic'

    def test_level_3_discriminant(self):
        r = even_odd_classification()
        assert r['level_3_discriminant']['match']
        assert r['level_3_discriminant']['discriminant'] == 20


# ── Discriminant Tower ───────────────────────────────────────────────────────

class TestDiscriminantTower:

    def test_all_match(self):
        r = discriminant_tower(n_max=20)
        assert r['all_match']

    def test_known_values(self):
        r = discriminant_tower(n_max=6)
        tower = r['tower']
        # Delta_0 = L(0)^2 - 4*(1) = 4 - 4 = 0, and 5*F(0)^2 = 0
        assert tower[0]['discriminant'] == 0
        # Delta_1 = L(1)^2 - 4*(-1) = 1 + 4 = 5
        assert tower[1]['discriminant'] == 5
        # Delta_2 = L(2)^2 - 4*(1) = 9 - 4 = 5
        assert tower[2]['discriminant'] == 5
        # Delta_3 = L(3)^2 - 4*(-1) = 16 + 4 = 20
        assert tower[3]['discriminant'] == 20


# ── Icosahedral Dihedral ────────────────────────────────────────────────────

class TestIcosahedralDihedral:

    def test_tan_equals_2_over_sqrt5(self):
        r = icosahedral_dihedral()
        assert r['tan_equals_2_over_sqrt5']

    def test_pythagorean(self):
        r = icosahedral_dihedral()
        assert r['pythagorean_verified']

    def test_algebraic_forms_equal(self):
        r = icosahedral_dihedral()
        assert r['algebraic_forms']['all_equal']

    def test_angle_in_degrees(self):
        r = icosahedral_dihedral()
        theta = r['dihedral_angle']['degrees']
        # Icosahedral dihedral angle ~ 138.19 degrees
        assert 138.0 < theta < 139.0


# ── Pell Equation ────────────────────────────────────────────────────────────

class TestPellEquation:

    def test_all_solutions_verified(self):
        r = pell_equation_15()
        assert r['all_verified']

    def test_fundamental_solution(self):
        r = pell_equation_15()
        f = r['fundamental_solution']
        assert f['x'] == 4 and f['y'] == 1
        assert 4 ** 2 - 15 * 1 ** 2 == 1

    def test_e8_connection(self):
        r = pell_equation_15()
        e8 = r['e8_connection']
        assert e8 is not None
        assert e8['y_equals_2_times_248']
        assert e8['x_equals_17_times_113']
        assert e8['pell_verified']

    def test_bridge_primes(self):
        r = pell_equation_15()
        assert r['bridge_primes']['both_bridge']

    def test_explicit_check_1921_496(self):
        assert 1921 ** 2 - 15 * 496 ** 2 == 1


# ── Class Number ─────────────────────────────────────────────────────────────

class TestClassNumber:

    def test_class_number_is_2(self):
        r = class_number_Q_sqrt15()
        assert r['class_number'] == 2
        assert r['class_group'] == 'Z/2Z'

    def test_fundamental_unit_norm(self):
        r = class_number_Q_sqrt15()
        assert r['fundamental_unit']['norm_equals_1']

    def test_field_discriminant(self):
        r = class_number_Q_sqrt15()
        assert r['field_discriminant'] == 60

    def test_ideal_square_is_principal(self):
        r = class_number_Q_sqrt15()
        assert r['non_principal_ideal']['ideal_square_is_principal']


# ── Subfield Data ────────────────────────────────────────────────────────────

class TestSubfieldData:

    def test_three_subfields(self):
        assert len(SUBFIELD_DATA) == 3

    def test_class_numbers(self):
        assert SUBFIELD_DATA['Q(sqrt(3))']['class_number'] == 1
        assert SUBFIELD_DATA['Q(sqrt(5))']['class_number'] == 1
        assert SUBFIELD_DATA['Q(sqrt(15))']['class_number'] == 2

    def test_discriminants(self):
        assert SUBFIELD_DATA['Q(sqrt(3))']['discriminant'] == 12
        assert SUBFIELD_DATA['Q(sqrt(5))']['discriminant'] == 5
        assert SUBFIELD_DATA['Q(sqrt(15))']['discriminant'] == 60


# ── Full Report ──────────────────────────────────────────────────────────────

class TestFullReport:

    def test_all_verifications_pass(self):
        r = full_polynomial_tower_report(n_max=8)
        assert r['all_verifications_pass']

    def test_summary_keys(self):
        r = full_polynomial_tower_report(n_max=5)
        s = r['summary']
        assert 'roots_verified' in s
        assert 'tower_disc_all_5Fn2' in s
        assert 'hierarchy_is_bridge_15' in s
        assert 'pell_all_verified' in s
