"""
Tests for perfect_cuboid.analysis — Structural analysis functions.

Tests framework patterns:
  - AX23 (Cascade): constraint propagation traces
  - AX34/M2 (Constitutive wound): modular obstruction measurement
  - AX37 (Holographic): self-similarity in Euler brick families
  - T14 (NeAynNeGayr): fidelity spectrum
  - AX22/T6 (Unreachable supremum): convergence bound < 1
"""

import pytest

from perfect_cuboid.analysis import (
    analyze_cascade,
    analyze_modular_obstruction,
    compute_fidelity_spectrum,
    check_holographic_property,
    CascadeAnalysis,
)
from perfect_cuboid.search import check_cuboid, euler_brick_search


class TestCascadeAnalysis:
    """AX23 — Cascade constraint propagation."""

    def test_euler_brick_cascade_depth_is_3(self):
        """An Euler brick satisfies 3 of 4 equations → depth 3."""
        ca = analyze_cascade(44, 117, 240)
        assert ca.eq1_satisfiable is True
        assert ca.eq2_satisfiable is True
        assert ca.eq3_satisfiable is True
        assert ca.eq4_satisfiable is False
        assert ca.cascade_depth == 3

    def test_random_triple_low_depth(self):
        """A random triple typically has low cascade depth."""
        ca = analyze_cascade(7, 11, 13)
        assert ca.cascade_depth <= 2

    def test_pythagorean_pair_only(self):
        """(3, 4, 100): a²+b²=5² but other equations fail."""
        ca = analyze_cascade(3, 4, 100)
        assert ca.eq1_satisfiable is True
        assert ca.cascade_depth >= 1

    def test_first_failure_identifies_break_point(self):
        """The first_failure field shows where cascade breaks."""
        ca = analyze_cascade(44, 117, 240)
        assert ca.first_failure == "eq4"

    def test_no_perfect_cuboid_depth_4(self):
        """No known triple has cascade_depth=4 — AX22 supremum."""
        for a in range(1, 50):
            for b in range(a, 50):
                for c in range(b, 50):
                    ca = analyze_cascade(a, b, c)
                    assert ca.cascade_depth < 4, \
                        f"Perfect cuboid found: ({a},{b},{c})!"

    def test_cascade_returns_correct_type(self):
        ca = analyze_cascade(3, 4, 5)
        assert isinstance(ca, CascadeAnalysis)
        assert ca.a == 3 and ca.b == 4 and ca.c == 5


class TestModularObstruction:
    """AX34/M2 — Modular analysis of the structural wound."""

    def test_euler_brick_modular_structure(self):
        """The smallest Euler brick (44,117,240) has known mod properties."""
        analysis = analyze_modular_obstruction(44, 117, 240)
        # mod 2: space diagonal sum = 44² + 117² + 240² = 1936 + 13689 + 57600 = 73225
        # 73225 mod 2 = 1; QR mod 2 = {0, 1}; so no obstruction mod 2
        assert analysis[2]["s_space_is_qr"] is True

    def test_obstruction_detection(self):
        """Find a triple where some prime creates obstruction."""
        analysis = analyze_modular_obstruction(1, 1, 1)
        # 1² + 1² + 1² = 3; is 3 a QR mod 4? QR mod 4 = {0, 1}; 3 not in set
        # But we're using primes, not 4. Let's check mod 7:
        # 3 mod 7 = 3; QR mod 7 = {0, 1, 2, 4}; 3 not in set → obstruction
        assert analysis[7]["modular_obstruction"] is True

    def test_qr_sets_computed(self):
        """Quadratic residue sets are computed correctly."""
        analysis = analyze_modular_obstruction(1, 1, 1)
        # QR mod 5 = {0, 1, 4}  (0²=0, 1²=1, 2²=4, 3²=4, 4²=1)
        assert analysis[5]["qr_set"] == {0, 1, 4}

    def test_all_primes_analyzed(self):
        """Default primes (2,3,5,7,11,13) are all analyzed."""
        analysis = analyze_modular_obstruction(44, 117, 240)
        assert set(analysis.keys()) == {2, 3, 5, 7, 11, 13}

    def test_custom_primes(self):
        """Can supply custom prime list."""
        analysis = analyze_modular_obstruction(44, 117, 240, primes=(17, 19))
        assert set(analysis.keys()) == {17, 19}


class TestFidelitySpectrum:
    """T14/AX21 — Fidelity distribution over the continuous spectrum."""

    def test_spectrum_covers_all_levels(self):
        """All fidelity levels 0/4 through 3/4 should appear; 4/4 should not."""
        spectrum = compute_fidelity_spectrum(20)
        fidelities = {entry["fidelity"] for entry in spectrum}
        assert 0.0 in fidelities       # plenty of zeroes
        assert 0.25 in fidelities       # some single-equation triples
        assert 0.75 in fidelities or 0.5 in fidelities  # at least some partial
        # 1.0 should have count=0 (no perfect cuboid)
        for entry in spectrum:
            if entry["fidelity"] == 1.0:
                assert entry["count"] == 0

    def test_funnel_shape(self):
        """AX22 convergent funnel: count decreases as fidelity increases."""
        spectrum = compute_fidelity_spectrum(20)
        counts = {entry["fidelity"]: entry["count"] for entry in spectrum}
        # f=0.0 should dominate
        assert counts[0.0] > counts.get(0.25, 0)

    def test_proportions_sum_to_one(self):
        """Proportions should sum to 1.0."""
        spectrum = compute_fidelity_spectrum(10)
        total = sum(entry["proportion"] for entry in spectrum)
        assert abs(total - 1.0) < 1e-10

    def test_supremum_never_reached(self):
        """AX22/T6: F=1.0 count is always 0 in any range we can compute."""
        spectrum = compute_fidelity_spectrum(30)
        for entry in spectrum:
            if entry["equations_satisfied"] == 4:
                assert entry["count"] == 0, \
                    "Perfect cuboid found — open problem resolved!"


class TestHolographicProperty:
    """AX37 — Holographic self-similarity in Euler brick families."""

    def test_empty_input(self):
        result = check_holographic_property([])
        assert result["holographic_seed"] is False
        assert result["families"] == []

    def test_smallest_euler_brick_scaling(self):
        """(44,117,240) generates scaled Euler bricks: k*(44,117,240)."""
        bricks = euler_brick_search(250)
        result = check_holographic_property(bricks)
        assert result["total_families"] >= 1

    def test_primitive_detection(self):
        """Primitive Euler bricks are identified as seeds."""
        brick = check_cuboid(44, 117, 240)
        result = check_holographic_property([brick])
        family = result["families"][0]
        # GCD(44,117,240) = 1, so primitive == the brick itself
        assert family["primitive"] == (44, 117, 240)
        assert family["scaling_factor"] == 1

    def test_multiples_are_euler_bricks(self):
        """If (a,b,c) is Euler, then (2a,2b,2c) is also Euler (AX37 seed)."""
        r = check_cuboid(88, 234, 480)  # 2*(44,117,240)
        assert r.is_euler_brick is True
