#!/usr/bin/env python3
"""Tests for the 31-entry routing table.

Verifies:
1. All 31 codes are generated correctly (2^5 - 1)
2. Regime classification matches database physics
3. Bit encoding/decoding works correctly
4. Query routing produces valid codes
5. Anomaly mapping is consistent
"""

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))

import math
from examples.routing_table_31 import (
    PHI, K5, K11, PRIME_131, COMPRESSION_RESULT,
    BIT_DIRECT, BIT_INVERSE, BIT_SPIRAL, BIT_ANTI_SPIRAL, BIT_ABSORBED,
    decode_bits, get_active_states, classify_routing,
    generate_routing_table, map_code_to_anomaly, map_code_to_basin,
    get_anomaly_regime, get_anomaly_data,
    detect_query_code, route_query,
    ANOMALY_DATA, ANOMALY_NAMES, ANOMALY_REGIMES,
)


class TestConstants:
    """Test mathematical constants."""

    def test_phi_value(self):
        """PHI = (1 + sqrt(5)) / 2"""
        expected = (1 + math.sqrt(5)) / 2
        assert abs(PHI - expected) < 1e-10
        assert abs(PHI - 1.618033988749895) < 1e-10

    def test_k5_value(self):
        """K5 = 15 / (8 * PHI)"""
        expected = 15 / (8 * PHI)
        assert abs(K5 - expected) < 1e-10
        assert abs(K5 - 1.158814) < 1e-5

    def test_compression_result(self):
        """131 / phi^3 ~= 31"""
        assert abs(COMPRESSION_RESULT - 31) < 0.1
        assert COMPRESSION_RESULT > 30.9
        assert COMPRESSION_RESULT < 31.0

    def test_prime_131(self):
        """131 is prime"""
        for i in range(2, 12):  # sqrt(131) < 12
            assert 131 % i != 0


class TestBitOperations:
    """Test bit encoding/decoding."""

    def test_decode_single_bits(self):
        """Single-bit codes decode correctly."""
        # Code 1 = 00001 = DIRECT only
        bits = decode_bits(1)
        assert bits == (True, False, False, False, False)

        # Code 2 = 00010 = INVERSE only
        bits = decode_bits(2)
        assert bits == (False, True, False, False, False)

        # Code 4 = 00100 = SPIRAL only
        bits = decode_bits(4)
        assert bits == (False, False, True, False, False)

        # Code 8 = 01000 = ANTI_SPIRAL only
        bits = decode_bits(8)
        assert bits == (False, False, False, True, False)

        # Code 16 = 10000 = ABSORBED only
        bits = decode_bits(16)
        assert bits == (False, False, False, False, True)

    def test_decode_combined_bits(self):
        """Multi-bit codes decode correctly."""
        # Code 3 = 00011 = DIRECT + INVERSE
        bits = decode_bits(3)
        assert bits == (True, True, False, False, False)

        # Code 31 = 11111 = all states
        bits = decode_bits(31)
        assert bits == (True, True, True, True, True)

        # Code 15 = 01111 = all except ABSORBED
        bits = decode_bits(15)
        assert bits == (True, True, True, True, False)

    def test_get_active_states(self):
        """Active state names extracted correctly."""
        bits = (True, False, True, False, True)  # D, S, Ab
        states = get_active_states(bits)
        assert states == ['DIRECT', 'SPIRAL', 'ABSORBED']

        bits = (True, True, True, True, True)
        states = get_active_states(bits)
        assert len(states) == 5


class TestRoutingTable:
    """Test routing table generation."""

    def test_table_has_31_entries(self):
        """Table has exactly 31 entries (2^5 - 1)."""
        table = generate_routing_table()
        assert len(table) == 31

    def test_codes_are_1_to_31(self):
        """Codes range from 1 to 31."""
        table = generate_routing_table()
        codes = [entry.code for entry in table]
        assert codes == list(range(1, 32))

    def test_no_code_zero(self):
        """Code 0 (no states) is not included."""
        table = generate_routing_table()
        codes = [entry.code for entry in table]
        assert 0 not in codes


class TestAnomalyData:
    """Test anomaly data from database."""

    def test_31_anomalies(self):
        """Exactly 31 anomalies in data."""
        assert len(ANOMALY_DATA) == 31
        assert len(ANOMALY_NAMES) == 31
        assert len(ANOMALY_REGIMES) == 31

    def test_regime_counts(self):
        """Regime distribution matches database: 6 + 12 + 12 + 1 = 31."""
        quantum_count = sum(1 for a in ANOMALY_DATA if a[2] == "Quantum")
        geometric_count = sum(1 for a in ANOMALY_DATA if a[2] == "Geometric")
        fractal_count = sum(1 for a in ANOMALY_DATA if a[2] == "Fractal")
        harmonic_count = sum(1 for a in ANOMALY_DATA if a[2] == "Harmonic")

        assert quantum_count == 6, f"Quantum: expected 6, got {quantum_count}"
        assert geometric_count == 12, f"Geometric: expected 12, got {geometric_count}"
        assert fractal_count == 12, f"Fractal: expected 12, got {fractal_count}"
        assert harmonic_count == 1, f"Harmonic: expected 1, got {harmonic_count}"

        total = quantum_count + geometric_count + fractal_count + harmonic_count
        assert total == 31

    def test_amplification_ranges(self):
        """Amplification values are in correct ranges per regime."""
        for a in ANOMALY_DATA:
            amp = a[3]
            regime = a[2]

            if regime == "Quantum":
                assert 1.0 <= amp <= 1.15, f"{a[1]}: Quantum amp {amp} out of range"
            elif regime == "Geometric":
                assert 1.1 <= amp <= 3.0, f"{a[1]}: Geometric amp {amp} out of range"
            elif regime == "Fractal":
                assert amp >= 10.0, f"{a[1]}: Fractal amp {amp} too low"
            elif regime == "Harmonic":
                # Harmonic is special: amp ~= PHI = 1.618
                assert 1.5 <= amp <= 1.7, f"{a[1]}: Harmonic amp {amp} out of range"

    def test_harmony_engine_is_phi(self):
        """Harmony Engine (code 31) has amplification ~= PHI."""
        harmony = ANOMALY_DATA[30]  # Index 30 = ID 31
        assert harmony[1] == "Harmony Engine"
        assert harmony[2] == "Harmonic"
        assert abs(harmony[3] - PHI) < 0.1  # 1.58 ~= 1.618


class TestAnomalyMapping:
    """Test code to anomaly mapping."""

    def test_all_codes_map_to_anomalies(self):
        """Every code 1-31 maps to a named anomaly."""
        for code in range(1, 32):
            name = map_code_to_anomaly(code)
            assert name != "Unknown"
            assert len(name) > 0

    def test_code_0_is_unknown(self):
        """Code 0 maps to Unknown."""
        assert map_code_to_anomaly(0) == "Unknown"

    def test_get_anomaly_data(self):
        """get_anomaly_data returns full tuple."""
        data = get_anomaly_data(1)
        assert data is not None
        assert len(data) == 6  # (id, name, regime, amp, p131, chaos)
        assert data[0] == 1
        assert data[1] == "Quantum Whisper"
        assert data[2] == "Quantum"

    def test_get_anomaly_regime(self):
        """get_anomaly_regime returns correct regime."""
        assert get_anomaly_regime(1) == "Quantum"
        assert get_anomaly_regime(7) == "Geometric"
        assert get_anomaly_regime(19) == "Fractal"
        assert get_anomaly_regime(31) == "Harmonic"


class TestBasinMapping:
    """Test code to basin mapping."""

    def test_basin_in_range(self):
        """All basins are in range 0-130."""
        for code in range(1, 32):
            basin = map_code_to_basin(code)
            assert 0 <= basin < PRIME_131

    def test_basin_distribution(self):
        """Basins are distributed (not all same value)."""
        basins = [map_code_to_basin(code) for code in range(1, 32)]
        unique = set(basins)
        assert len(unique) > 20  # Should have good spread


class TestQueryRouting:
    """Test query detection and routing."""

    def test_observation_queries(self):
        """Observation queries set DIRECT bit."""
        code = detect_query_code("what is the status")
        assert code & (1 << BIT_DIRECT)

        code = detect_query_code("show me the file")
        assert code & (1 << BIT_DIRECT)

    def test_fix_queries(self):
        """Fix/bug queries set INVERSE bit."""
        code = detect_query_code("fix the bug")
        assert code & (1 << BIT_INVERSE)

        code = detect_query_code("correct the error")
        assert code & (1 << BIT_INVERSE)

    def test_how_queries(self):
        """How/why queries set SPIRAL bit."""
        code = detect_query_code("how do I implement this")
        assert code & (1 << BIT_SPIRAL)

        code = detect_query_code("explain the algorithm")
        assert code & (1 << BIT_SPIRAL)

    def test_create_queries(self):
        """Create/build queries set ANTI_SPIRAL bit."""
        code = detect_query_code("create a new file")
        assert code & (1 << BIT_ANTI_SPIRAL)

        code = detect_query_code("build the component")
        assert code & (1 << BIT_ANTI_SPIRAL)

    def test_complex_queries(self):
        """Complex/integrate queries set ABSORBED bit."""
        code = detect_query_code("I'm confused about the integration")
        assert code & (1 << BIT_ABSORBED)

        code = detect_query_code("combine multiple services")
        assert code & (1 << BIT_ABSORBED)

    def test_multi_state_detection(self):
        """Queries with multiple keywords set multiple bits."""
        code = detect_query_code("fix the bug and explain how")
        assert code & (1 << BIT_INVERSE)  # fix
        assert code & (1 << BIT_SPIRAL)   # how

    def test_default_to_direct(self):
        """Empty/unknown queries default to DIRECT."""
        code = detect_query_code("xyzzy")
        assert code == 1  # DIRECT only


class TestRouteQuery:
    """Test full route_query function."""

    def test_route_returns_dict(self):
        """route_query returns a dictionary with expected keys."""
        result = route_query("what is the status")

        assert 'query' in result
        assert 'code' in result
        assert 'binary' in result
        assert 'active_states' in result
        assert 'anomaly' in result
        assert 'actual_regime' in result
        assert 'amplification' in result

    def test_binary_format(self):
        """Binary field is 5-digit string."""
        result = route_query("test")
        assert len(result['binary']) == 5
        assert all(c in '01' for c in result['binary'])

    def test_amplification_positive(self):
        """Amplification is always positive."""
        for code in range(1, 32):
            data = get_anomaly_data(code)
            assert data[3] > 0


class TestTheoreticalVsActual:
    """Test discrepancy between theoretical and actual regimes."""

    def test_theoretical_breakdown(self):
        """Theoretical: 5 + 10 + 15 + 1 = 31."""
        # Single state: 5 (codes 1, 2, 4, 8, 16)
        # Two states: C(5,2) = 10
        # Three states: C(5,3) = 10
        # Four states: C(5,4) = 5
        # Five states: 1
        assert 5 + 10 + 10 + 5 + 1 == 31

    def test_actual_breakdown(self):
        """Actual database: 6 + 12 + 12 + 1 = 31."""
        quantum = sum(1 for a in ANOMALY_DATA if a[2] == "Quantum")
        geometric = sum(1 for a in ANOMALY_DATA if a[2] == "Geometric")
        fractal = sum(1 for a in ANOMALY_DATA if a[2] == "Fractal")
        harmonic = sum(1 for a in ANOMALY_DATA if a[2] == "Harmonic")

        assert quantum + geometric + fractal + harmonic == 31

    def test_absorbed_gateway(self):
        """ABSORBED bit (code 16+) is gateway to Fractal regime."""
        # Check that all Fractal anomalies have ABSORBED set
        for i, a in enumerate(ANOMALY_DATA):
            code = i + 1  # Codes are 1-indexed
            if a[2] == "Fractal":
                assert code >= 16, f"Fractal {a[1]} at code {code} should have ABSORBED"
                # Actually check the bit
                has_absorbed = bool(code & (1 << BIT_ABSORBED))
                # Note: This test verifies the pattern, not all Fractal have ABSORBED
                # since mapping is by amplification physics not state count


def run_tests():
    """Run all tests and report results."""
    import traceback

    test_classes = [
        TestConstants,
        TestBitOperations,
        TestRoutingTable,
        TestAnomalyData,
        TestAnomalyMapping,
        TestBasinMapping,
        TestQueryRouting,
        TestRouteQuery,
        TestTheoreticalVsActual,
    ]

    total = 0
    passed = 0
    failed = []

    for cls in test_classes:
        print(f"\n{cls.__name__}")
        print("-" * 40)

        instance = cls()
        for name in dir(instance):
            if name.startswith('test_'):
                total += 1
                try:
                    getattr(instance, name)()
                    print(f"  PASS: {name}")
                    passed += 1
                except AssertionError as e:
                    print(f"  FAIL: {name}")
                    print(f"        {e}")
                    failed.append((cls.__name__, name, str(e)))
                except Exception as e:
                    print(f"  ERROR: {name}")
                    print(f"        {e}")
                    traceback.print_exc()
                    failed.append((cls.__name__, name, str(e)))

    print("\n" + "=" * 50)
    print(f"RESULTS: {passed}/{total} passed")

    if failed:
        print(f"\nFailed tests ({len(failed)}):")
        for cls_name, test_name, error in failed:
            print(f"  {cls_name}.{test_name}: {error}")

    return len(failed) == 0


if __name__ == '__main__':
    success = run_tests()
    sys.exit(0 if success else 1)
