"""Tests for the bridge_field module.

Tests cover:
- QBiquad arithmetic (addition, subtraction, multiplication, scaling)
- Multiplication table verification
- Galois automorphisms (sigma_1 through sigma_4)
- Full and relative norms
- Subfield content decomposition
- Framework constant verification
- Galois orbit analysis
- Gamma_0(15) modular data
- Prime splitting patterns
- Full analysis
"""

import math
import pytest

from watkins_nn.constants import PHI, PHI_INV, PHI_SQUARED, SQRT5
from watkins_nn.bridge_field import (
    QBiquad,
    SQRT3, SQRT15, TWO_SQRT3, LN_PHI, L1_CHI5,
    PHI_Q, PHI_INV_Q, PHI_SQ_Q,
    TWO_SQRT3_Q, EQUILATERAL_HEIGHT_Q, SQRT15_Q,
    ZERO_Q, ONE_Q,
    verify_multiplication_table,
    verify_framework_constants,
    galois_orbit_analysis,
    gamma0_15_data,
    prime_splitting,
    run_full_analysis,
)


EPS = 1e-12


# ── QBiquad Arithmetic ──────────────────────────────────────────────────────

class TestQBiquadArithmetic:

    def test_value(self):
        x = QBiquad(1, 2, 3, 4)
        expected = 1 + 2 * SQRT3 + 3 * SQRT5 + 4 * SQRT15
        assert abs(x.value - expected) < EPS

    def test_zero_value(self):
        assert abs(ZERO_Q.value) < EPS

    def test_one_value(self):
        assert abs(ONE_Q.value - 1.0) < EPS

    def test_addition(self):
        a = QBiquad(1, 2, 3, 4)
        b = QBiquad(5, 6, 7, 8)
        c = a + b
        assert abs(c.a - 6) < EPS
        assert abs(c.b - 8) < EPS
        assert abs(c.c - 10) < EPS
        assert abs(c.d - 12) < EPS

    def test_subtraction(self):
        a = QBiquad(5, 6, 7, 8)
        b = QBiquad(1, 2, 3, 4)
        c = a - b
        assert abs(c.a - 4) < EPS
        assert abs(c.b - 4) < EPS
        assert abs(c.c - 4) < EPS
        assert abs(c.d - 4) < EPS

    def test_multiplication_by_one(self):
        x = QBiquad(1, 2, 3, 4)
        result = x * ONE_Q
        assert abs(result.a - x.a) < EPS
        assert abs(result.b - x.b) < EPS
        assert abs(result.c - x.c) < EPS
        assert abs(result.d - x.d) < EPS

    def test_multiplication_sqrt3_squared(self):
        """sqrt(3) * sqrt(3) = 3."""
        s3 = QBiquad(0, 1, 0, 0)
        result = s3 * s3
        assert abs(result.a - 3) < EPS
        assert abs(result.b) < EPS
        assert abs(result.c) < EPS
        assert abs(result.d) < EPS

    def test_multiplication_sqrt5_squared(self):
        """sqrt(5) * sqrt(5) = 5."""
        s5 = QBiquad(0, 0, 1, 0)
        result = s5 * s5
        assert abs(result.a - 5) < EPS

    def test_multiplication_sqrt15_squared(self):
        """sqrt(15) * sqrt(15) = 15."""
        s15 = QBiquad(0, 0, 0, 1)
        result = s15 * s15
        assert abs(result.a - 15) < EPS

    def test_multiplication_cross_sqrt3_sqrt5(self):
        """sqrt(3) * sqrt(5) = sqrt(15)."""
        s3 = QBiquad(0, 1, 0, 0)
        s5 = QBiquad(0, 0, 1, 0)
        result = s3 * s5
        assert abs(result.d - 1) < EPS
        assert abs(result.a) < EPS
        assert abs(result.b) < EPS
        assert abs(result.c) < EPS

    def test_scale(self):
        x = QBiquad(1, 2, 3, 4)
        y = x.scale(2)
        assert abs(y.a - 2) < EPS
        assert abs(y.b - 4) < EPS
        assert abs(y.c - 6) < EPS
        assert abs(y.d - 8) < EPS

    def test_phi_times_phi_inv_is_1(self):
        product = PHI_Q * PHI_INV_Q
        assert abs(product.value - 1.0) < EPS


# ── Galois Automorphisms ────────────────────────────────────────────────────

class TestGaloisAutomorphisms:

    def test_sigma1_is_identity(self):
        x = QBiquad(1, 2, 3, 4)
        y = x.sigma1()
        assert abs(y.a - x.a) < EPS
        assert abs(y.b - x.b) < EPS
        assert abs(y.c - x.c) < EPS
        assert abs(y.d - x.d) < EPS

    def test_sigma2_negates_sqrt3(self):
        x = QBiquad(1, 2, 3, 4)
        y = x.sigma2()
        assert abs(y.a - 1) < EPS
        assert abs(y.b - (-2)) < EPS
        assert abs(y.c - 3) < EPS
        assert abs(y.d - (-4)) < EPS

    def test_sigma3_negates_sqrt5(self):
        x = QBiquad(1, 2, 3, 4)
        y = x.sigma3()
        assert abs(y.a - 1) < EPS
        assert abs(y.b - 2) < EPS
        assert abs(y.c - (-3)) < EPS
        assert abs(y.d - (-4)) < EPS

    def test_sigma4_negates_both(self):
        x = QBiquad(1, 2, 3, 4)
        y = x.sigma4()
        assert abs(y.a - 1) < EPS
        assert abs(y.b - (-2)) < EPS
        assert abs(y.c - (-3)) < EPS
        assert abs(y.d - 4) < EPS

    def test_sigma4_preserves_sqrt15(self):
        """sigma_4 maps sqrt(15) -> sqrt(15) (bridge survives)."""
        s15 = SQRT15_Q
        y = s15.sigma4()
        assert abs(y.value - s15.value) < EPS

    def test_sigma2_sigma3_equals_sigma4(self):
        """sigma_2 composed with sigma_3 = sigma_4."""
        x = QBiquad(1, 2, 3, 4)
        y_23 = x.sigma2().sigma3()
        y_4 = x.sigma4()
        assert abs(y_23.a - y_4.a) < EPS
        assert abs(y_23.b - y_4.b) < EPS
        assert abs(y_23.c - y_4.c) < EPS
        assert abs(y_23.d - y_4.d) < EPS

    def test_phi_lives_in_Q_sqrt5(self):
        """phi = 1/2 + (1/2)*sqrt(5) is fixed by sigma_2 restricted to Q(sqrt5)."""
        orbit = PHI_Q.galois_orbit
        # sigma_2 changes sqrt3 sign but preserves sqrt5
        # phi has no sqrt3 content, so sigma_2(phi) = phi
        assert abs(orbit[0] - orbit[1]) < EPS  # sigma1 = sigma2 for pure Q(sqrt5)

    def test_sqrt3_lives_in_Q_sqrt3(self):
        orbit = TWO_SQRT3_Q.galois_orbit
        # sigma_3 preserves sqrt3, so sigma1 = sigma3
        assert abs(orbit[0] - orbit[2]) < EPS


# ── Norms ────────────────────────────────────────────────────────────────────

class TestNorms:

    def test_full_norm_of_phi(self):
        """N(phi) = phi * phi_bar = -1, so full norm = (-1)^2 * ... no.
        Full norm = sigma1*sigma2*sigma3*sigma4 = phi * phi * (-phi_bar) * (-phi_bar)
        = phi^2 * phi_bar^2 = (phi*phi_bar)^2 = (-1)^2 = 1.
        Wait: phi in Q(sqrt5), so sigma2(phi) = phi, sigma3(phi) = conjugate of phi in Q(sqrt5).
        sigma3 negates sqrt5: phi = 1/2 + 1/2*sqrt5 -> 1/2 - 1/2*sqrt5 = phi_bar.
        Full norm = phi * phi * phi_bar * phi_bar = (phi*phi_bar)^2 = 1.
        """
        nf = PHI_Q.norm_full
        assert abs(nf - 1.0) < 1e-10

    def test_norm_of_sqrt15(self):
        """N(sqrt15) = sqrt15 * (-sqrt15) * (-sqrt15) * sqrt15 = 15^2 = 225."""
        nf = SQRT15_Q.norm_full
        assert abs(nf - 225.0) < 1e-8

    def test_relative_norm_sqrt5_of_phi(self):
        """N_{Q(s3,s5)/Q(s5)}(phi) = phi * sigma2(phi) = phi * phi = phi^2."""
        n5 = PHI_Q.norm_Q_sqrt5
        assert abs(n5 - PHI_SQUARED) < 1e-10

    def test_galois_orbit_length(self):
        orbit = PHI_Q.galois_orbit
        assert len(orbit) == 4


# ── Multiplication Table ────────────────────────────────────────────────────

class TestMultiplicationTable:

    def test_all_entries_pass(self):
        table = verify_multiplication_table()
        for key, val in table.items():
            assert val, f"Multiplication table failed: {key}"


# ── Framework Constants ─────────────────────────────────────────────────────

class TestFrameworkConstants:

    def test_all_constants_verified(self):
        fc = verify_framework_constants()
        for name, data in fc.items():
            if 'match' in data:
                assert data['match'], f"Framework constant failed: {name}"

    def test_phi_value(self):
        assert abs(PHI_Q.value - PHI) < EPS

    def test_phi_inv_value(self):
        assert abs(PHI_INV_Q.value - PHI_INV) < EPS

    def test_phi_sq_value(self):
        assert abs(PHI_SQ_Q.value - PHI_SQUARED) < EPS

    def test_equilateral_height(self):
        assert abs(EQUILATERAL_HEIGHT_Q.value - SQRT3 / 2.0) < EPS


# ── Galois Orbit Analysis ───────────────────────────────────────────────────

class TestGaloisOrbitAnalysis:

    def test_phi_in_Q_sqrt5(self):
        orbits = galois_orbit_analysis()
        assert orbits['phi']['lives_in_Q_sqrt5']

    def test_2sqrt3_in_Q_sqrt3(self):
        orbits = galois_orbit_analysis()
        assert orbits['2*sqrt3']['lives_in_Q_sqrt3']

    def test_sqrt15_is_bridge(self):
        orbits = galois_orbit_analysis()
        assert orbits['sqrt15']['lives_in_Q_sqrt15']


# ── Gamma_0(15) ─────────────────────────────────────────────────────────────

class TestGamma015:

    def test_genus_1(self):
        data = gamma0_15_data()
        assert data['genus'] == 1
        assert data['is_elliptic_curve']

    def test_index(self):
        data = gamma0_15_data()
        assert data['index_in_SL2Z'] == 24

    def test_cusps(self):
        data = gamma0_15_data()
        assert data['number_of_cusps'] == 4


# ── Prime Splitting ─────────────────────────────────────────────────────────

class TestPrimeSplitting:

    def test_ramification_at_3(self):
        r = prime_splitting(3)
        assert r['subfields']['Q(sqrt3)']['type'] == 'ramifies'

    def test_ramification_at_5(self):
        r = prime_splitting(5)
        assert r['subfields']['Q(sqrt5)']['type'] == 'ramifies'

    def test_bridge_prime_7(self):
        """(15/7) should be computed correctly."""
        r = prime_splitting(7)
        # 15 mod 7 = 1, and 1 is a QR, so (15/7) = 1
        assert r['subfields']['Q(sqrt15)']['symbol'] == 1
        assert r['is_bridge_prime']

    def test_inert_at_2_for_Q_sqrt3(self):
        """Q(sqrt(3)): D=3, 3 mod 8 = 3, so 2 is inert (symbol = -1).
        Wait: D=3 is odd. 3 mod 8 = 3 -> (3/2) uses D mod 8:
        D mod 8 = 3, which is not 1 or 7, so symbol = -1 -> inert.
        But actually D=3 for Q(sqrt3), disc=12. For p=2: 2|12 -> ramifies.
        """
        r = prime_splitting(2)
        # D=3 for Q(sqrt3): 3 is odd, 3 mod 8 = 3 -> inert
        # But wait: the function checks D%2 first. 3%2 = 1, not 0.
        # So it uses D mod 8 rule. 3 mod 8 = 3 -> inert.
        assert r['subfields']['Q(sqrt3)']['type'] == 'inert'

    def test_invalid_prime_raises(self):
        with pytest.raises(ValueError):
            prime_splitting(1)


# ── Algebraic Constants ─────────────────────────────────────────────────────

class TestAlgebraicConstants:

    def test_sqrt3(self):
        assert abs(SQRT3 ** 2 - 3) < EPS

    def test_sqrt15(self):
        assert abs(SQRT15 ** 2 - 15) < EPS

    def test_sqrt15_equals_sqrt3_times_sqrt5(self):
        assert abs(SQRT15 - SQRT3 * SQRT5) < EPS

    def test_two_sqrt3(self):
        assert abs(TWO_SQRT3 - 2 * SQRT3) < EPS

    def test_L1_chi5(self):
        """L(1, chi_5) = 2*ln(phi)/sqrt(5)."""
        expected = 2 * math.log(PHI) / SQRT5
        assert abs(L1_CHI5 - expected) < EPS


# ── Full Analysis ────────────────────────────────────────────────────────────

class TestFullAnalysis:

    def test_runs_without_error(self):
        r = run_full_analysis()
        assert 'multiplication_table' in r
        assert 'framework_constants' in r
        assert 'galois_orbits' in r
        assert 'gamma0_15' in r

    def test_multiplication_table_all_pass(self):
        r = run_full_analysis()
        for key, val in r['multiplication_table'].items():
            assert val, f"Failed: {key}"
