"""Identity Preservation & Omnidirectional Flow Validation

This test suite validates the core mathematical properties that make
Cubesquare different from RAG/embeddings:

1. IDENTITY PRESERVATION: P - P = 0, P / P = 1
2. OMNIDIRECTIONAL FLOW: Not just forward/backward, but all 5 directions
3. DETERMINISTIC ROUNDTRIP: Same content -> same coordinates, always
4. ALGEBRAIC CLOSURE: All operations stay within the system

RAG Problem: Embeddings drift, approximate, collapse at scale
CST Solution: Deterministic coordinates with algebraic identity
"""

import pytest
from cubesquare import CSTMemory, Token, morton_encode, morton_decode, compute_basin
from cubesquare.i_logic import ILogicState
from cubesquare.i_logic.algebra import multiply, compose, get_inverse, verify_algebra


class TestIdentityPreservation:
    """P - P = 0 (home), P / P = 1 (identity)"""

    def test_pp_equals_zero_home_position(self):
        """P - P = 0: Any token minus itself returns to origin."""
        mem = CSTMemory()
        token = mem.write("/src/auth/login.py")

        # Decode to coordinates
        x, y, z = morton_decode(token.morton)

        # P - P = 0 (return to origin)
        home_x = x - x
        home_y = y - y
        home_z = z - z

        assert home_x == 0
        assert home_y == 0
        assert home_z == 0

        # Origin encodes to 0
        origin_morton = morton_encode(0, 0, 0)
        assert origin_morton == 0

    def test_pp_equals_one_identity(self):
        """P / P = 1: Any token divided by itself equals identity."""
        mem = CSTMemory()
        token = mem.write("/src/database/query.py")

        x, y, z = morton_decode(token.morton)

        # Avoid division by zero, but conceptually P/P = 1
        if x != 0:
            assert x / x == 1.0
        if y != 0:
            assert y / y == 1.0
        # z might be 0, that's ok

        # In I-Logic: DIRECT is the identity (value = 1)
        assert ILogicState.DIRECT == 1

    def test_roundtrip_identity_preservation(self):
        """Same content always produces same coordinates."""
        content = "/path/to/file.py"

        # Create multiple tokens for same content
        mem1 = CSTMemory()
        mem2 = CSTMemory()

        token1 = mem1.write(content)
        token2 = mem2.write(content)

        # CRITICAL: Same content -> Same identity
        assert token1.morton == token2.morton
        assert token1.hex_id == token2.hex_id
        assert token1.basin == token2.basin
        assert token1.layer == token2.layer

    def test_identity_survives_transformation(self):
        """Identity preserved through encode/decode cycles."""
        original_coords = (12345, 67890, 42)

        # Forward: coords -> morton
        morton = morton_encode(*original_coords)

        # Backward: morton -> coords
        decoded = morton_decode(morton)

        # Identity preserved
        assert decoded == original_coords

        # Multiple cycles
        for _ in range(100):
            morton = morton_encode(*decoded)
            decoded = morton_decode(morton)
            assert decoded == original_coords


class TestOmnidirectionalFlow:
    """5 directions, not just 2 (forward/backward)."""

    def test_five_states_exist(self):
        """All 5 I-Logic states are distinct."""
        states = ILogicState.ALL_STATES
        assert len(states) == 5
        assert len(set(states)) == 5  # All unique

        # Verify the 5 directions (complex number representation)
        assert ILogicState.DIRECT == 1       # Forward (identity)
        assert ILogicState.INVERSE == -1     # Backward (reflection)
        assert ILogicState.SPIRAL == 1j      # Expansion (+i rotation)
        assert ILogicState.ANTI_SPIRAL == -1j # Contraction (-i rotation)
        assert ILogicState.ABSORBED == 0     # Origin (termination)

    def test_omnidirectional_multiplication(self):
        """Each state can transition to any other state."""
        I = ILogicState

        # From DIRECT (identity), can reach all states
        assert multiply(I.DIRECT, I.DIRECT) == I.DIRECT
        assert multiply(I.DIRECT, I.INVERSE) == I.INVERSE
        assert multiply(I.DIRECT, I.SPIRAL) == I.SPIRAL
        assert multiply(I.DIRECT, I.ANTI_SPIRAL) == I.ANTI_SPIRAL
        assert multiply(I.DIRECT, I.ABSORBED) == I.ABSORBED

        # From SPIRAL (expansion), can reach multiple states
        assert multiply(I.SPIRAL, I.SPIRAL) == I.INVERSE  # +i * +i = -1
        assert multiply(I.SPIRAL, I.ANTI_SPIRAL) == I.DIRECT  # +i * -i = 1
        assert multiply(I.SPIRAL, I.INVERSE) == I.ANTI_SPIRAL  # +i * -1 = -i

    def test_not_just_bidirectional(self):
        """Prove this isn't just forward/backward."""
        I = ILogicState

        # Bidirectional would only have: forward, backward
        # We have 5 distinct directions with different behaviors

        # SPIRAL cycles (not reversible like simple forward/backward)
        path = [I.SPIRAL, I.SPIRAL, I.SPIRAL, I.SPIRAL]
        result, _ = compose(path)
        assert result == I.DIRECT  # Completes a cycle

        # 2 spirals = INVERSE (90° + 90° = 180°)
        result, _ = compose([I.SPIRAL, I.SPIRAL])
        assert result == I.INVERSE

        # 3 spirals = ANTI_SPIRAL (90° + 90° + 90° = 270° = -90°)
        result, _ = compose([I.SPIRAL, I.SPIRAL, I.SPIRAL])
        assert result == I.ANTI_SPIRAL

    def test_spiral_geometry(self):
        """SPIRAL/ANTI_SPIRAL represent rotation, not just +/-."""
        I = ILogicState

        # Complex number interpretation:
        # DIRECT = 1, INVERSE = -1, SPIRAL = +i, ANTI_SPIRAL = -i

        # i * i = -1 (SPIRAL * SPIRAL = INVERSE)
        assert multiply(I.SPIRAL, I.SPIRAL) == I.INVERSE

        # -i * -i = -1 (ANTI_SPIRAL * ANTI_SPIRAL = INVERSE)
        assert multiply(I.ANTI_SPIRAL, I.ANTI_SPIRAL) == I.INVERSE

        # i * -i = 1 (SPIRAL * ANTI_SPIRAL = DIRECT)
        assert multiply(I.SPIRAL, I.ANTI_SPIRAL) == I.DIRECT

        # This is quaternion-like behavior, omnidirectional

    def test_absorbed_as_origin(self):
        """ABSORBED is the origin/sink - all paths terminate here."""
        I = ILogicState

        # Any state * ABSORBED = ABSORBED (absorbing element)
        for state in I.ALL_STATES:
            assert multiply(state, I.ABSORBED) == I.ABSORBED
            assert multiply(I.ABSORBED, state) == I.ABSORBED


class TestAlgebraicClosure:
    """All operations stay within the system."""

    def test_multiplication_closure(self):
        """Any two states multiply to a valid state."""
        states = ILogicState.ALL_STATES

        for a in states:
            for b in states:
                result = multiply(a, b)
                assert result in states, f"{a} * {b} = {result} not in states"

    def test_composition_closure(self):
        """Any path composes to a valid state."""
        I = ILogicState

        # Random paths of varying lengths
        paths = [
            [I.DIRECT],
            [I.SPIRAL, I.SPIRAL],
            [I.INVERSE, I.INVERSE, I.INVERSE],
            [I.SPIRAL, I.ANTI_SPIRAL, I.SPIRAL, I.INVERSE],
            [I.SPIRAL] * 10,
        ]

        for path in paths:
            result, _ = compose(path)
            assert result in I.ALL_STATES

    def test_inverse_existence(self):
        """Non-ABSORBED states have inverses (back to DIRECT)."""
        I = ILogicState

        # DIRECT is self-inverse
        assert get_inverse(I.DIRECT) == I.DIRECT
        assert multiply(I.DIRECT, get_inverse(I.DIRECT)) == I.DIRECT

        # INVERSE is self-inverse
        assert get_inverse(I.INVERSE) == I.INVERSE
        assert multiply(I.INVERSE, get_inverse(I.INVERSE)) == I.DIRECT

        # SPIRAL and ANTI_SPIRAL are mutual inverses
        assert get_inverse(I.SPIRAL) == I.ANTI_SPIRAL
        assert get_inverse(I.ANTI_SPIRAL) == I.SPIRAL
        assert multiply(I.SPIRAL, I.ANTI_SPIRAL) == I.DIRECT

        # ABSORBED has no inverse (it's the zero element)
        assert get_inverse(I.ABSORBED) is None

    def test_full_algebra_verification(self):
        """Complete algebraic property verification."""
        assert verify_algebra(), "Algebra verification failed"


class TestBasinLayerDeterminism:
    """Basin and Layer assignment is deterministic."""

    def test_basin_deterministic(self):
        """Same coordinates always map to same basin."""
        test_coords = [
            (100, 200, 0),
            (12345, 67890, 42),
            (0, 0, 0),
            (255, 255, 255),
        ]

        for coords in test_coords:
            basin1 = compute_basin(*coords)
            basin2 = compute_basin(*coords)
            basin3 = compute_basin(*coords)

            assert basin1 == basin2 == basin3
            assert 0 <= basin1 <= 130  # Fernandez basin range

    def test_basin_from_token_deterministic(self):
        """Token basin is deterministic from content."""
        content = "/unique/path/to/module.py"

        mem = CSTMemory()
        tokens = [mem.write(content) for _ in range(5)]

        # All should be the same token (cached)
        basins = [t.basin for t in tokens]
        assert len(set(basins)) == 1  # All identical

    def test_layer_from_basin(self):
        """Layer is derived from basin deterministically."""
        # Layer = basin // 14.5 + 1 (approximately)
        # Basin 0-14 -> L1, 15-29 -> L2, etc.

        mem = CSTMemory()

        # Write multiple tokens and verify layer consistency
        paths = [f"/test/file{i}.py" for i in range(100)]
        tokens = [mem.write(p) for p in paths]

        for token in tokens:
            # Layer should be 1-9
            assert 1 <= token.layer <= 9

            # Layer should be derivable from basin
            expected_layer = min(token.basin // 15 + 1, 9)
            # Note: actual formula may vary, just verify consistency
            assert token.layer >= 1


class TestRAGComparisonAdvantages:
    """Demonstrate advantages over RAG/embeddings."""

    def test_no_drift(self):
        """Unlike embeddings, coordinates don't drift over time."""
        content = "authentication module"

        # Simulate "time passing" with multiple memory instances
        results = []
        for _ in range(100):
            mem = CSTMemory()
            token = mem.write(content)
            results.append((token.morton, token.hex_id, token.basin))

        # ALL results must be identical - no drift
        assert len(set(results)) == 1

    def test_no_collision_at_scale(self):
        """Distinct content stays distinct (no equidistance problem)."""
        mem = CSTMemory()

        # Generate many distinct paths
        tokens = []
        for i in range(1000):
            token = mem.write(f"/project/module{i}/file{i % 10}.py")
            tokens.append(token)

        # All morton codes should be unique (or very few collisions)
        morton_codes = [t.morton for t in tokens]
        hex_ids = [t.hex_id for t in tokens]

        # Check uniqueness (allowing for hash collision, but should be rare)
        unique_mortons = len(set(morton_codes))
        unique_hexes = len(set(hex_ids))

        # At least 95% unique (hash collisions are rare but possible)
        assert unique_mortons >= 950
        assert unique_hexes >= 950

    def test_reversible_unlike_embeddings(self):
        """Can go from token back to coordinates (embeddings can't)."""
        mem = CSTMemory()
        token = mem.write("/src/core/engine.py")

        # Forward: content -> morton -> coords
        x, y, z = morton_decode(token.morton)

        # Backward: coords -> morton
        reconstructed = morton_encode(x, y, z)

        # Perfect reconstruction
        assert reconstructed == token.morton

        # Embeddings can't do this - they're lossy projections

    def test_scale_invariant(self):
        """Performance doesn't degrade with corpus size."""
        mem = CSTMemory()

        # Write 1000 tokens
        for i in range(1000):
            mem.write(f"/large/corpus/file{i}.py")

        # Query should still work (not searching through all)
        import time

        start = time.perf_counter()
        new_token = mem.write("/new/path/test.py")
        elapsed = time.perf_counter() - start

        # Should be sub-millisecond (O(1), not O(N))
        assert elapsed < 0.01  # 10ms max


class TestMathematicalIdentities:
    """Verify core mathematical identities."""

    def test_complex_number_mapping(self):
        """I-Logic states map to complex unit circle."""
        I = ILogicState

        # Mapping: 1, -1, +i, -i, 0
        # These are the 4th roots of unity + zero

        # 1^4 = 1 (DIRECT)
        result, _ = compose([I.DIRECT] * 4)
        assert result == I.DIRECT

        # i^4 = 1 (SPIRAL cycles back)
        result, _ = compose([I.SPIRAL] * 4)
        assert result == I.DIRECT

        # (-1)^2 = 1 (INVERSE self-resolves)
        result, _ = compose([I.INVERSE] * 2)
        assert result == I.DIRECT

        # (-i)^4 = 1 (ANTI_SPIRAL cycles back)
        result, _ = compose([I.ANTI_SPIRAL] * 4)
        assert result == I.DIRECT

    def test_group_properties(self):
        """Verify group-like properties (except ABSORBED breaks it)."""
        I = ILogicState
        non_zero = [I.DIRECT, I.INVERSE, I.SPIRAL, I.ANTI_SPIRAL]

        # Closure (already tested)

        # Associativity: (a*b)*c = a*(b*c)
        for a in non_zero:
            for b in non_zero:
                for c in non_zero:
                    left = multiply(multiply(a, b), c)
                    right = multiply(a, multiply(b, c))
                    assert left == right, f"({a}*{b})*{c} != {a}*({b}*{c})"

        # Identity: DIRECT is identity
        for a in non_zero:
            assert multiply(a, I.DIRECT) == a
            assert multiply(I.DIRECT, a) == a

        # Inverse exists for non-zero elements
        for a in non_zero:
            inv = get_inverse(a)
            assert inv is not None
            assert multiply(a, inv) == I.DIRECT


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
