"""
Tests for the Diophantine Fidelity Funnel discovery module.

Tests the serendipitous finding: the fidelity funnel conjecture
and its KV₇ independence prerequisite.
"""

import pytest
from fidelity_funnel import (
    DiophantineEquation,
    EquationSystem,
    FidelityLevel,
    FunnelResult,
    compute_fidelity_funnel,
    check_independence_heuristic,
    is_perfect_square,
    is_perfect_cube,
    is_triangular,
    perfect_cuboid_system,
    three_pythagorean_system,
    four_variable_allpairs_system,
    algebraic_mixed_system,
    fibonacci_inspired_system,
    correlated_system,
    CONJECTURE,
)


# ===========================================================================
# Helper function tests
# ===========================================================================

class TestHelpers:
    def test_is_perfect_square_true(self):
        for n in [0, 1, 4, 9, 16, 25, 100, 144, 10000]:
            assert is_perfect_square(n), f"{n} should be a perfect square"

    def test_is_perfect_square_false(self):
        for n in [2, 3, 5, 7, 8, 10, 15, 99]:
            assert not is_perfect_square(n), f"{n} should not be a perfect square"

    def test_is_perfect_square_negative(self):
        assert not is_perfect_square(-4)

    def test_is_perfect_cube_true(self):
        for n in [0, 1, 8, 27, 64, 125, 1000]:
            assert is_perfect_cube(n), f"{n} should be a perfect cube"

    def test_is_perfect_cube_false(self):
        for n in [2, 3, 4, 7, 9, 26, 63]:
            assert not is_perfect_cube(n), f"{n} should not be a perfect cube"

    def test_is_triangular_true(self):
        # T(k) = k*(k+1)/2: 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55
        for n in [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]:
            assert is_triangular(n), f"{n} should be triangular"

    def test_is_triangular_false(self):
        for n in [2, 4, 5, 7, 8, 9, 11, 14, 20]:
            assert not is_triangular(n), f"{n} should not be triangular"


# ===========================================================================
# Equation system construction tests
# ===========================================================================

class TestEquationSystems:
    def test_cuboid_system_has_4_equations(self):
        sys = perfect_cuboid_system()
        assert sys.k == 4
        assert sys.n_variables == 3

    def test_three_pyth_system_has_3_equations(self):
        sys = three_pythagorean_system()
        assert sys.k == 3

    def test_four_var_system_has_6_equations(self):
        sys = four_variable_allpairs_system()
        assert sys.k == 6
        assert sys.n_variables == 4

    def test_correlated_system_has_implied_equation(self):
        sys = correlated_system()
        assert sys.k == 3
        # eq2 should always agree with eq1 on Pythagorean triples
        eq1 = sys.equations[0].predicate
        eq2 = sys.equations[1].predicate
        # (3,4) is Pythagorean: 9+16=25
        assert eq1(3, 4) is True
        assert eq2(3, 4) is True  # 4*25=100, which IS a perfect square

    def test_cuboid_equations_detect_pythagorean(self):
        sys = perfect_cuboid_system()
        # (3,4,c) — first equation should pass: 9+16=25
        assert sys.equations[0].predicate(3, 4, 7) is True

    def test_cuboid_equations_reject_non_pythagorean(self):
        sys = perfect_cuboid_system()
        assert sys.equations[0].predicate(2, 3, 4) is False  # 4+9=13


# ===========================================================================
# Fidelity funnel computation tests
# ===========================================================================

class TestFidelityFunnel:
    def test_cuboid_funnel_is_monotonic(self):
        """The Perfect Cuboid system should show a monotonic funnel."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=20)
        assert result.is_monotonic is True
        assert result.violation_at is None

    def test_three_pyth_funnel_is_monotonic(self):
        """Three Pythagorean system should be monotonic."""
        sys = three_pythagorean_system()
        result = compute_fidelity_funnel(sys, limit=20)
        assert result.is_monotonic is True

    def test_fibonacci_funnel_is_monotonic(self):
        """Fibonacci-inspired system should be monotonic."""
        sys = fibonacci_inspired_system()
        result = compute_fidelity_funnel(sys, limit=20)
        assert result.is_monotonic is True

    def test_algebraic_funnel_is_monotonic(self):
        """Algebraic mixed system should be monotonic."""
        sys = algebraic_mixed_system()
        result = compute_fidelity_funnel(sys, limit=40)
        assert result.is_monotonic is True

    def test_correlated_funnel_violates_monotonicity(self):
        """The correlated system (KV₇ violation) should break monotonicity."""
        sys = correlated_system()
        result = compute_fidelity_funnel(sys, limit=50)
        assert result.is_monotonic is False
        assert result.violation_at is not None

    def test_funnel_levels_sum_to_total(self):
        """All fidelity levels must sum to total tuples."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=15)
        level_sum = sum(lv.count for lv in result.levels)
        assert level_sum == result.total_tuples

    def test_funnel_proportions_sum_to_one(self):
        """All proportions must sum to approximately 1.0."""
        sys = three_pythagorean_system()
        result = compute_fidelity_funnel(sys, limit=15)
        prop_sum = sum(lv.proportion for lv in result.levels)
        assert abs(prop_sum - 1.0) < 1e-10

    def test_funnel_fidelity_values_correct(self):
        """Fidelity values should be m/k."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=10)
        for lv in result.levels:
            assert abs(lv.fidelity - lv.m / lv.k) < 1e-10

    def test_max_fidelity_bounded_below_one(self):
        """AX22/T6: The supremum (fidelity = 1.0) should not be reached
        for the cuboid system (no perfect cuboid exists in search range)."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=20)
        assert result.max_fidelity_observed < 1.0

    def test_level_zero_dominates(self):
        """D(0) should be the largest count in any system."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=15)
        assert result.levels[0].count >= max(
            lv.count for lv in result.levels[1:])


# ===========================================================================
# Consecutive ratio tests
# ===========================================================================

class TestConsecutiveRatios:
    def test_ratios_all_above_one_for_monotonic(self):
        """For monotonic systems, all consecutive ratios should be ≥ 1."""
        sys = perfect_cuboid_system()
        result = compute_fidelity_funnel(sys, limit=20)
        ratios = result.consecutive_ratios()
        for r in ratios:
            if r is not None:
                assert r >= 1.0, f"Ratio {r} < 1 violates monotonicity"

    def test_ratios_contain_below_one_for_violated(self):
        """For the correlated system, at least one ratio should be < 1."""
        sys = correlated_system()
        result = compute_fidelity_funnel(sys, limit=50)
        ratios = result.consecutive_ratios()
        real_ratios = [r for r in ratios if r is not None]
        assert any(r < 1.0 for r in real_ratios), \
            "Correlated system should have at least one ratio < 1"


# ===========================================================================
# Independence checker tests (KV₇)
# ===========================================================================

class TestIndependence:
    def test_cuboid_equations_approximately_independent(self):
        """The cuboid equations should pass the independence heuristic."""
        sys = perfect_cuboid_system()
        result = check_independence_heuristic(sys, limit=15)
        assert result["kv7_satisfied"] is True

    def test_correlated_equations_detected(self):
        """The correlated system should fail KV₇."""
        sys = correlated_system()
        result = check_independence_heuristic(sys, limit=20)
        assert result["kv7_satisfied"] is False
        assert result["max_correlation_ratio"] > 2.0

    def test_marginals_are_probabilities(self):
        """All marginal probabilities should be in [0, 1]."""
        sys = three_pythagorean_system()
        result = check_independence_heuristic(sys, limit=15)
        for name, p in result["marginals"].items():
            assert 0 <= p <= 1, f"Marginal for {name} = {p} out of range"


# ===========================================================================
# KV₇ ↔ Monotonicity correspondence tests
# ===========================================================================

class TestKV7MonotonicityCorrespondence:
    """Test the central claim: independence (KV₇) ↔ monotonicity."""

    def test_independent_implies_monotonic_cuboid(self):
        sys = perfect_cuboid_system()
        indep = check_independence_heuristic(sys, limit=15)
        funnel = compute_fidelity_funnel(sys, limit=15)
        if indep["kv7_satisfied"]:
            assert funnel.is_monotonic

    def test_independent_implies_monotonic_three_pyth(self):
        sys = three_pythagorean_system()
        indep = check_independence_heuristic(sys, limit=15)
        funnel = compute_fidelity_funnel(sys, limit=15)
        if indep["kv7_satisfied"]:
            assert funnel.is_monotonic

    def test_dependent_may_violate_correlated(self):
        sys = correlated_system()
        indep = check_independence_heuristic(sys, limit=20)
        funnel = compute_fidelity_funnel(sys, limit=50)
        assert indep["kv7_satisfied"] is False
        assert funnel.is_monotonic is False

    def test_independent_implies_monotonic_fibonacci(self):
        sys = fibonacci_inspired_system()
        indep = check_independence_heuristic(sys, limit=15)
        funnel = compute_fidelity_funnel(sys, limit=15)
        if indep["kv7_satisfied"]:
            assert funnel.is_monotonic


# ===========================================================================
# Conjecture statement tests
# ===========================================================================

class TestConjectureStatement:
    def test_conjecture_is_nonempty(self):
        assert len(CONJECTURE) > 100

    def test_conjecture_mentions_kv7(self):
        assert "KV" in CONJECTURE

    def test_conjecture_mentions_independence(self):
        assert "independence" in CONJECTURE.lower()

    def test_conjecture_mentions_monotonic(self):
        assert "monoton" in CONJECTURE.lower()

    def test_conjecture_states_boundary(self):
        assert "BOUNDARY" in CONJECTURE

    def test_conjecture_states_grade(self):
        assert "Tasdik" in CONJECTURE
