"""The Biquadratic Bridge Field Q(sqrt(3), sqrt(5)).

The Watkins framework lives in the biquadratic number field
Q(sqrt(3), sqrt(5)), a degree-4 extension of Q with three quadratic subfields:

    Q(sqrt(3))  -- "pi-world":  scaffolds the conservation simplex
    Q(sqrt(5))  -- "phi-world": generates the golden ratio and threshold
    Q(sqrt(15)) -- "bridge":    sqrt(15) = sqrt(3) * sqrt(5) connects both

Every element of Q(sqrt(3), sqrt(5)) can be written uniquely as:

    a + b*sqrt(3) + c*sqrt(5) + d*sqrt(15),    a, b, c, d in Q

The Galois group is V_4 = Z_2 x Z_2, with four automorphisms:
    sigma_1 (id):     sqrt(3) -> +sqrt(3), sqrt(5) -> +sqrt(5)
    sigma_2:          sqrt(3) -> -sqrt(3), sqrt(5) -> +sqrt(5)
    sigma_3:          sqrt(3) -> +sqrt(3), sqrt(5) -> -sqrt(5)
    sigma_4:          sqrt(3) -> -sqrt(3), sqrt(5) -> -sqrt(5)

Key algebraic data:
    - Subfield class numbers: h(Q(sqrt3))=1, h(Q(sqrt5))=1, h(Q(sqrt15))=2
    - Fundamental units: 2+sqrt(3), phi, 4+sqrt(15)
    - Discriminants: 12, 5, 60
    - Prime splitting governed by quadratic reciprocity for 3, 5, 15
"""

import math
from typing import Dict, List, Tuple, Any, Optional
from dataclasses import dataclass

from .constants import (
    PHI, PHI_INV, PHI_SQUARED, SQRT5,
    CONSERVATION_EPSILON,
)


# =============================================================================
# FUNDAMENTAL IRRATIONAL CONSTANTS
# =============================================================================

SQRT3 = math.sqrt(3)
SQRT15 = math.sqrt(15)   # = sqrt(3) * sqrt(5)
LN_PHI = math.log(PHI)

# Dirichlet L-function: L(1, chi_5) = 2*ln(phi)/sqrt(5)
# From the class number formula for real quadratic field Q(sqrt(5)), disc = 5
L1_CHI5 = 2.0 * LN_PHI / SQRT5

# Eigenvalue ratio target (pure algebraic constant)
TWO_SQRT3 = 2.0 * SQRT3    # 2*sqrt(3) = 3.46410...


# =============================================================================
# 1. Q(sqrt(3), sqrt(5)) ELEMENT TYPE
# =============================================================================

@dataclass(frozen=True)
class QBiquad:
    """Element of the biquadratic field Q(sqrt(3), sqrt(5)).

    Representation: a + b*sqrt(3) + c*sqrt(5) + d*sqrt(15)

    This is a 4-dimensional Q-vector space with multiplication table:
        sqrt(3) * sqrt(3) = 3
        sqrt(5) * sqrt(5) = 5
        sqrt(15) * sqrt(15) = 15
        sqrt(3) * sqrt(5) = sqrt(15)
        sqrt(3) * sqrt(15) = 3*sqrt(5)
        sqrt(5) * sqrt(15) = 5*sqrt(3)
    """
    a: float  # rational part
    b: float  # coefficient of sqrt(3)
    c: float  # coefficient of sqrt(5)
    d: float  # coefficient of sqrt(15)

    @property
    def value(self) -> float:
        """Numerical value a + b*sqrt(3) + c*sqrt(5) + d*sqrt(15)."""
        return self.a + self.b * SQRT3 + self.c * SQRT5 + self.d * SQRT15

    def sigma1(self) -> 'QBiquad':
        """Identity automorphism: sqrt(3) -> +sqrt(3), sqrt(5) -> +sqrt(5).
        Fixed field: Q(sqrt(3), sqrt(5)).
        """
        return QBiquad(self.a, self.b, self.c, self.d)

    def sigma2(self) -> 'QBiquad':
        """Negate sqrt(3): sqrt(3) -> -sqrt(3), sqrt(5) -> +sqrt(5).
        Fixed field: Q(sqrt(5)).
        """
        return QBiquad(self.a, -self.b, self.c, -self.d)

    def sigma3(self) -> 'QBiquad':
        """Negate sqrt(5): sqrt(3) -> +sqrt(3), sqrt(5) -> -sqrt(5).
        Fixed field: Q(sqrt(3)).
        """
        return QBiquad(self.a, self.b, -self.c, -self.d)

    def sigma4(self) -> 'QBiquad':
        """Negate both: sqrt(3) -> -sqrt(3), sqrt(5) -> -sqrt(5).
        Bridge sqrt(15) -> +sqrt(15) SURVIVES.
        Fixed field: Q(sqrt(15)).
        """
        return QBiquad(self.a, -self.b, -self.c, self.d)

    def __add__(self, other: 'QBiquad') -> 'QBiquad':
        return QBiquad(
            self.a + other.a,
            self.b + other.b,
            self.c + other.c,
            self.d + other.d,
        )

    def __sub__(self, other: 'QBiquad') -> 'QBiquad':
        return QBiquad(
            self.a - other.a,
            self.b - other.b,
            self.c - other.c,
            self.d - other.d,
        )

    def __mul__(self, other: 'QBiquad') -> 'QBiquad':
        """Multiply using the biquadratic multiplication table.

        Cross-products:
            s3*s3 = 3           s5*s5 = 5           s15*s15 = 15
            s3*s5 = s15         s3*s15 = 3*s5       s5*s15 = 5*s3
        """
        a1, b1, c1, d1 = self.a, self.b, self.c, self.d
        a2, b2, c2, d2 = other.a, other.b, other.c, other.d

        return QBiquad(
            a=a1 * a2 + 3 * b1 * b2 + 5 * c1 * c2 + 15 * d1 * d2,
            b=a1 * b2 + b1 * a2 + 5 * c1 * d2 + 5 * d1 * c2,
            c=a1 * c2 + c1 * a2 + 3 * b1 * d2 + 3 * d1 * b2,
            d=a1 * d2 + d1 * a2 + b1 * c2 + c1 * b2,
        )

    def scale(self, q: float) -> 'QBiquad':
        """Multiply by a rational scalar."""
        return QBiquad(q * self.a, q * self.b, q * self.c, q * self.d)

    @property
    def norm_full(self) -> float:
        """Full field norm: product over all four Galois conjugates.

        N(x) = sigma1(x) * sigma2(x) * sigma3(x) * sigma4(x)
        Computed numerically as product of conjugate values.
        """
        v1 = self.sigma1().value
        v2 = self.sigma2().value
        v3 = self.sigma3().value
        v4 = self.sigma4().value
        return v1 * v2 * v3 * v4

    @property
    def norm_Q_sqrt3(self) -> float:
        """Relative norm from Q(sqrt(3),sqrt(5)) down to Q(sqrt(3)).
        N_{Q(s3,s5)/Q(s3)}(x) = x * sigma2(x)
        Wait: sigma2 fixes Q(sqrt5), so Gal(Q(s3,s5)/Q(s5)) = {id, sigma2}.
        Actually sigma2 negates sqrt3, so its fixed field is Q(sqrt5).
        The norm down to Q(sqrt3) uses the automorphism that fixes Q(sqrt3),
        which is sigma3.
        N_{Q(s3,s5)/Q(s3)}(x) = x * sigma3(x).
        """
        return self.value * self.sigma3().value

    @property
    def norm_Q_sqrt5(self) -> float:
        """Relative norm from Q(sqrt(3),sqrt(5)) down to Q(sqrt(5)).
        N_{Q(s3,s5)/Q(s5)}(x) = x * sigma2(x).
        """
        return self.value * self.sigma2().value

    @property
    def norm_Q_sqrt15(self) -> float:
        """Relative norm from Q(sqrt(3),sqrt(5)) down to Q(sqrt(15)).
        Gal(Q(s3,s5)/Q(s15)) = {id, sigma4} since sigma4 fixes sqrt(15).
        N_{Q(s3,s5)/Q(s15)}(x) = x * sigma4(x).
        """
        return self.value * self.sigma4().value

    @property
    def subfield_content(self) -> Dict[str, float]:
        """Decompose into subfield contributions.

        Returns the fraction of the total value from each subfield:
            Q:         |a| / |value|
            Q(sqrt3):  |b*sqrt3| / |value|
            Q(sqrt5):  |c*sqrt5| / |value|
            Q(sqrt15): |d*sqrt15| / |value|
        """
        v = abs(self.value)
        if v < 1e-15:
            return {"Q": 0.0, "Q(sqrt3)": 0.0, "Q(sqrt5)": 0.0, "Q(sqrt15)": 0.0}
        return {
            "Q": abs(self.a) / v,
            "Q(sqrt3)": abs(self.b * SQRT3) / v,
            "Q(sqrt5)": abs(self.c * SQRT5) / v,
            "Q(sqrt15)": abs(self.d * SQRT15) / v,
        }

    @property
    def galois_orbit(self) -> Tuple[float, float, float, float]:
        """The four Galois conjugate values."""
        return (
            self.sigma1().value,
            self.sigma2().value,
            self.sigma3().value,
            self.sigma4().value,
        )

    def __repr__(self) -> str:
        parts = []
        if abs(self.a) > 1e-14:
            parts.append(f"{self.a:.6f}")
        if abs(self.b) > 1e-14:
            sign = "+" if self.b > 0 and parts else ""
            parts.append(f"{sign}{self.b:.6f}*sqrt3")
        if abs(self.c) > 1e-14:
            sign = "+" if self.c > 0 and parts else ""
            parts.append(f"{sign}{self.c:.6f}*sqrt5")
        if abs(self.d) > 1e-14:
            sign = "+" if self.d > 0 and parts else ""
            parts.append(f"{sign}{self.d:.6f}*sqrt15")
        if not parts:
            return "QBiquad(0)"
        return f"QBiquad({' '.join(parts)})"


# =============================================================================
# 2. FRAMEWORK CONSTANTS IN Q(sqrt(3), sqrt(5))
# =============================================================================

# --- Pure Q(sqrt(5)) elements ---
PHI_Q = QBiquad(a=0.5, b=0.0, c=0.5, d=0.0)             # phi = 1/2 + (1/2)*sqrt(5)
PHI_INV_Q = QBiquad(a=-0.5, b=0.0, c=0.5, d=0.0)        # 1/phi = -1/2 + (1/2)*sqrt(5)
PHI_SQ_Q = QBiquad(a=1.5, b=0.0, c=0.5, d=0.0)          # phi^2 = 3/2 + (1/2)*sqrt(5)

# --- Pure Q(sqrt(3)) elements ---
TWO_SQRT3_Q = QBiquad(a=0.0, b=2.0, c=0.0, d=0.0)       # 2*sqrt(3)
EQUILATERAL_HEIGHT_Q = QBiquad(a=0.0, b=0.5, c=0.0, d=0.0)  # sqrt(3)/2

# --- Pure Q(sqrt(15)) element ---
SQRT15_Q = QBiquad(a=0.0, b=0.0, c=0.0, d=1.0)          # sqrt(15) = sqrt(3)*sqrt(5)

# --- Zero and unity ---
ZERO_Q = QBiquad(a=0.0, b=0.0, c=0.0, d=0.0)
ONE_Q = QBiquad(a=1.0, b=0.0, c=0.0, d=0.0)


# =============================================================================
# 3. MULTIPLICATION TABLE VERIFICATION
# =============================================================================

def verify_multiplication_table() -> Dict[str, bool]:
    """Verify the Q(sqrt(3), sqrt(5)) multiplication table.

    sqrt(3) * sqrt(3) = 3
    sqrt(5) * sqrt(5) = 5
    sqrt(15) * sqrt(15) = 15
    sqrt(3) * sqrt(5) = sqrt(15)
    sqrt(3) * sqrt(15) = 3*sqrt(5)
    sqrt(5) * sqrt(15) = 5*sqrt(3)
    """
    s3 = QBiquad(0, 1, 0, 0)     # sqrt(3)
    s5 = QBiquad(0, 0, 1, 0)     # sqrt(5)
    s15 = QBiquad(0, 0, 0, 1)    # sqrt(15)
    three = QBiquad(3, 0, 0, 0)
    five = QBiquad(5, 0, 0, 0)
    fifteen = QBiquad(15, 0, 0, 0)
    three_s5 = QBiquad(0, 0, 3, 0)    # 3*sqrt(5)
    five_s3 = QBiquad(0, 5, 0, 0)     # 5*sqrt(3)

    eps = 1e-14

    def eq(x: QBiquad, y: QBiquad) -> bool:
        return (abs(x.a - y.a) < eps and abs(x.b - y.b) < eps
                and abs(x.c - y.c) < eps and abs(x.d - y.d) < eps)

    return {
        "sqrt3 * sqrt3 = 3": eq(s3 * s3, three),
        "sqrt5 * sqrt5 = 5": eq(s5 * s5, five),
        "sqrt15 * sqrt15 = 15": eq(s15 * s15, fifteen),
        "sqrt3 * sqrt5 = sqrt15": eq(s3 * s5, s15),
        "sqrt3 * sqrt15 = 3*sqrt5": eq(s3 * s15, three_s5),
        "sqrt5 * sqrt15 = 5*sqrt3": eq(s5 * s15, five_s3),
    }


# =============================================================================
# 4. FRAMEWORK CONSTANT VERIFICATION
# =============================================================================

def verify_framework_constants() -> Dict[str, Dict[str, Any]]:
    """Verify that framework constants live in Q(sqrt(3), sqrt(5)) as claimed.

    phi = 1/2 + (1/2)*sqrt(5)           -> (1/2, 0, 1/2, 0)
    1/phi = -1/2 + (1/2)*sqrt(5)        -> (-1/2, 0, 1/2, 0)
    phi^2 = 3/2 + (1/2)*sqrt(5)         -> (3/2, 0, 1/2, 0)
    2*sqrt(3) (algebraic constant)      -> (0, 2, 0, 0)
    sqrt(15) (bridge)                   -> (0, 0, 0, 1)
    equilateral height sqrt(3)/2        -> (0, 1/2, 0, 0)
    """
    results = {}
    eps = 1e-14

    checks = [
        ("phi", PHI_Q, PHI),
        ("1/phi", PHI_INV_Q, PHI_INV),
        ("phi^2", PHI_SQ_Q, PHI_SQUARED),
        ("2*sqrt3", TWO_SQRT3_Q, TWO_SQRT3),
        ("sqrt15", SQRT15_Q, SQRT15),
        ("equilateral_height", EQUILATERAL_HEIGHT_Q, SQRT3 / 2.0),
    ]

    for name, q_elem, target in checks:
        val = q_elem.value
        results[name] = {
            "coords": (q_elem.a, q_elem.b, q_elem.c, q_elem.d),
            "value": val,
            "target": target,
            "error": abs(val - target),
            "match": abs(val - target) < eps,
            "subfield_content": q_elem.subfield_content,
        }

    # Verify phi * (1/phi) = 1
    product = PHI_Q * PHI_INV_Q
    results["phi * 1/phi = 1"] = {
        "product_coords": (product.a, product.b, product.c, product.d),
        "product_value": product.value,
        "match": abs(product.value - 1.0) < eps,
    }

    # Verify phi^2 = phi + 1
    phi_plus_1 = PHI_Q + ONE_Q
    results["phi^2 = phi + 1"] = {
        "phi_sq_coords": (PHI_SQ_Q.a, PHI_SQ_Q.b, PHI_SQ_Q.c, PHI_SQ_Q.d),
        "phi_plus_1_coords": (phi_plus_1.a, phi_plus_1.b, phi_plus_1.c, phi_plus_1.d),
        "match": abs(PHI_SQ_Q.value - phi_plus_1.value) < eps,
    }

    return results


# =============================================================================
# 5. GALOIS ORBIT ANALYSIS
# =============================================================================

def galois_orbit_analysis() -> Dict[str, Dict[str, Any]]:
    """Compute the Galois orbit of each framework constant.

    For each constant, the four Galois conjugate values reveal which
    subfields it depends on:
    - If sigma_2 changes the value: it has sqrt(3) content
    - If sigma_3 changes the value: it has sqrt(5) content
    - If sigma_4 changes the value: it has both (but not sqrt(15) only)
    - If sigma_4 preserves but sigma_2/sigma_3 don't: it's a bridge element
    """
    elements = {
        "phi": PHI_Q,
        "1/phi": PHI_INV_Q,
        "phi^2": PHI_SQ_Q,
        "2*sqrt3": TWO_SQRT3_Q,
        "sqrt15": SQRT15_Q,
    }

    results = {}
    for name, elem in elements.items():
        orbit = elem.galois_orbit
        # sigma2 fixes Q(sqrt5), sigma3 fixes Q(sqrt3), sigma4 fixes Q(sqrt15)
        changed_by_sigma2 = abs(orbit[0] - orbit[1]) > 1e-10
        changed_by_sigma3 = abs(orbit[0] - orbit[2]) > 1e-10
        changed_by_sigma4 = abs(orbit[0] - orbit[3]) > 1e-10
        results[name] = {
            "sigma1": orbit[0],
            "sigma2": orbit[1],
            "sigma3": orbit[2],
            "sigma4": orbit[3],
            "lives_in_Q": not changed_by_sigma2 and not changed_by_sigma3 and not changed_by_sigma4,
            # Lives in Q(sqrt3) iff sigma3 fixes it (no sqrt5 content)
            # but sigma2 changes it (has sqrt3 content)
            "lives_in_Q_sqrt3": changed_by_sigma2 and not changed_by_sigma3,
            # Lives in Q(sqrt5) iff sigma2 fixes it but sigma3 changes it
            "lives_in_Q_sqrt5": not changed_by_sigma2 and changed_by_sigma3,
            # Lives in Q(sqrt15) iff sigma4 fixes it (sqrt15 preserved)
            # but sigma2 and sigma3 both change it
            "lives_in_Q_sqrt15": changed_by_sigma2 and changed_by_sigma3 and not changed_by_sigma4,
        }

    return results


# =============================================================================
# 6. GAMMA_0(15) MODULAR DATA
# =============================================================================

def gamma0_15_data() -> Dict[str, Any]:
    """Data about the congruence subgroup Gamma_0(15) and X_0(15).

    The modular curve X_0(15) has genus 1, so it is an elliptic curve.
    The level N=15 = 3*5 connects the two primes that generate the
    biquadratic field Q(sqrt(3), sqrt(5)).

    Returns:
        Dict with group-theoretic data for Gamma_0(15).
    """
    # Index: psi(N) = N * prod_{p|N}(1 + 1/p) = 15 * (4/3) * (6/5) = 24
    index_exact = 24

    # Number of cusps: sum_{d|N} phi(gcd(d, N/d))
    # Divisors of 15: 1, 3, 5, 15 -> each gives phi(1) = 1 -> 4 cusps
    n_cusps = 4

    # nu_2 = 0 (no solution to x^2 + 1 = 0 mod 3)
    nu_2 = 0
    # nu_3 = 0 (no solution to x^2 + x + 1 = 0 mod 15)
    nu_3 = 0

    # Genus: g = 1 + index/12 - nu_2/4 - nu_3/3 - cusps/2
    genus = 1 + index_exact // 12 - nu_2 // 4 - nu_3 // 3 - n_cusps // 2

    return {
        "level": 15,
        "factorization": "3 * 5",
        "index_in_SL2Z": index_exact,
        "number_of_cusps": n_cusps,
        "nu_2_elliptic_order2": nu_2,
        "nu_3_elliptic_order3": nu_3,
        "genus": genus,
        "is_elliptic_curve": genus == 1,
        "selberg_bound": 0.25,
        "connection": (
            "X_0(15) is an elliptic curve (genus 1). The level 15 = 3*5 "
            "encodes the two primes whose square roots generate "
            "the biquadratic field Q(sqrt(3), sqrt(5))."
        ),
    }


# =============================================================================
# 7. PRIME SPLITTING PATTERNS
# =============================================================================

def prime_splitting(p: int) -> Dict[str, Any]:
    """Determine how a prime p splits in Q(sqrt(3)), Q(sqrt(5)), Q(sqrt(15)).

    A prime p splits in Q(sqrt(D)) iff (D/p) = +1 (Legendre symbol).
    It is inert iff (D/p) = -1, and ramifies iff p | D.

    Returns:
        Dict with splitting behaviour in all three subfields.
    """
    if p < 2:
        raise ValueError(f"p must be prime, got {p}")

    def legendre(a: int, p: int) -> int:
        """Compute the Legendre symbol (a/p) for odd prime p."""
        if p == 2:
            return 0  # special case handled separately
        a = a % p
        if a == 0:
            return 0
        # Euler's criterion
        val = pow(a, (p - 1) // 2, p)
        return 1 if val == 1 else -1

    results = {}
    for D, name in [(3, "Q(sqrt3)"), (5, "Q(sqrt5)"), (15, "Q(sqrt15)")]:
        if p == 2:
            # Special handling for p = 2
            if D % 2 == 0:
                results[name] = {"symbol": 0, "type": "ramifies"}
            else:
                # (D/2): D odd, check D mod 8
                d8 = D % 8
                if d8 == 1 or d8 == 7:
                    results[name] = {"symbol": 1, "type": "splits"}
                else:
                    results[name] = {"symbol": -1, "type": "inert"}
        elif D % p == 0:
            results[name] = {"symbol": 0, "type": "ramifies"}
        else:
            sym = legendre(D, p)
            if sym == 1:
                results[name] = {"symbol": 1, "type": "splits"}
            else:
                results[name] = {"symbol": -1, "type": "inert"}

    # A prime is a "bridge prime" if (15/p) = +1
    is_bridge = results["Q(sqrt15)"]["symbol"] == 1

    return {
        "prime": p,
        "subfields": results,
        "is_bridge_prime": is_bridge,
    }


# =============================================================================
# 8. COMPREHENSIVE ANALYSIS
# =============================================================================

def run_full_analysis() -> Dict[str, Any]:
    """Run the complete bridge field analysis and return all results."""
    results = {}

    results["multiplication_table"] = verify_multiplication_table()
    results["framework_constants"] = verify_framework_constants()
    results["galois_orbits"] = galois_orbit_analysis()
    results["gamma0_15"] = gamma0_15_data()

    return results


# =============================================================================
# EXPORTS
# =============================================================================

__all__ = [
    # Element type
    "QBiquad",
    # Constants
    "SQRT3", "SQRT15", "TWO_SQRT3", "LN_PHI", "L1_CHI5",
    # Framework constants in Q(sqrt(3), sqrt(5))
    "PHI_Q", "PHI_INV_Q", "PHI_SQ_Q",
    "TWO_SQRT3_Q", "EQUILATERAL_HEIGHT_Q", "SQRT15_Q",
    "ZERO_Q", "ONE_Q",
    # Verification functions
    "verify_multiplication_table",
    "verify_framework_constants",
    "galois_orbit_analysis",
    "gamma0_15_data",
    "prime_splitting",
    "run_full_analysis",
]
