#!/usr/bin/env python3
"""
E7: Echo Symbolization + Se Integration KAT

Tests for the Echo symbolization pipeline:
  KRISPER.symbolize() → S_e_of_state() → Braid.fuse()

Claims proven:
  - KRISPER.symbolize() is state-dependent (different state → different symbols)
  - S_e_of_state() formula: H(X) * C(X) * (1/D(X))
  - Symbolization is deterministic given same state
  - Integration with Se computes correctly

NOT CLAIMED:
  - Full Echo tick semantics (tested separately)
  - Guardian/Vow enforcement (tested separately)

Run: pytest tests/test_echo_symbolization_path.py -v
"""
import sys
import json
import hashlib
from pathlib import Path

import pytest

# Add paths
HELIX_CDC = Path(__file__).parent.parent
sys.path.insert(0, str(HELIX_CDC))
sys.path.insert(0, str(HELIX_CDC / "tools"))

from echo_reference import (
    SELF_STATE,
    KRISPER,
    Braid,
    S_e_of_state,
    shannon_entropy_counts,
    sha256_hex,
)


def create_test_state(
    soulfile_hash: str = None,
    lobes: dict = None,
    vault_refs: dict = None,
    drift: int = 0,
) -> SELF_STATE:
    """Create test state with given parameters."""
    if soulfile_hash is None:
        soulfile_hash = sha256_hex(b"test_initial")
    return SELF_STATE(
        timestamp=0.0,  # Fixed for determinism
        soulfile_hash=soulfile_hash,
        lobes=lobes or {},
        vault_refs=vault_refs or {},
        commit_ledger=[],
        drift=drift,
    )


class TestKRISPERSymbolize:
    """Test KRISPER.symbolize() behavior."""

    def test_symbolize_produces_symbols(self):
        """
        E7 Test #1: Symbolize returns list of symbol IDs.
        """
        state = create_test_state()
        input_text = "hello world test"

        symbols = KRISPER.symbolize(input_text, state)

        assert isinstance(symbols, list)
        assert len(symbols) == 3  # 3 tokens
        for s in symbols:
            assert isinstance(s, str)
            assert "_" in s  # format: base_hash

    def test_symbolize_state_dependent(self):
        """
        E7 Test #2: Same input, different state → different symbols.

        This proves symbolization is state-dependent via hashable_view().
        """
        input_text = "hello world"

        state1 = create_test_state(soulfile_hash=sha256_hex(b"state_a"))
        state2 = create_test_state(soulfile_hash=sha256_hex(b"state_b"))

        symbols1 = KRISPER.symbolize(input_text, state1)
        symbols2 = KRISPER.symbolize(input_text, state2)

        # Symbols should differ because states differ
        assert symbols1 != symbols2

        # But same structure (same number of tokens)
        assert len(symbols1) == len(symbols2)

    def test_symbolize_deterministic(self):
        """
        E7 Test #3: Same state + same input → same symbols.
        """
        state = create_test_state()
        input_text = "recall pattern alpha"

        symbols_run1 = KRISPER.symbolize(input_text, state)
        symbols_run2 = KRISPER.symbolize(input_text, state)

        assert symbols_run1 == symbols_run2

    def test_symbolize_empty_input(self):
        """
        E7 Test #4: Empty input returns empty list.
        """
        state = create_test_state()

        symbols = KRISPER.symbolize("", state)
        assert symbols == []

        symbols2 = KRISPER.symbolize("   ", state)
        assert symbols2 == []

    def test_symbolize_case_normalization(self):
        """
        E7 Test #5: Input is lowercased.
        """
        state = create_test_state()

        upper = KRISPER.symbolize("HELLO WORLD", state)
        lower = KRISPER.symbolize("hello world", state)
        mixed = KRISPER.symbolize("HeLLo WoRLd", state)

        assert upper == lower == mixed

    def test_symbolize_includes_state_hash(self):
        """
        E7 Test #6: Each symbol includes state hash prefix.
        """
        state = create_test_state()
        state_hash = sha256_hex(state.hashable_view())[:8]

        symbols = KRISPER.symbolize("test input", state)

        for s in symbols:
            # Format: base_tokenhash
            parts = s.split("_")
            assert len(parts) == 2
            assert parts[0] == state_hash


class TestSeOfState:
    """Test S_e_of_state() entropy formula."""

    def test_se_basic_computation(self):
        """
        E7 Test #7: S_e computes without error.
        """
        state = create_test_state()

        se = S_e_of_state(state)

        assert isinstance(se, float)
        assert se >= 0  # Entropy is non-negative

    def test_se_formula_components(self):
        """
        E7 Test #8: S_e = H * C * (1/D) where:
          H = Shannon entropy of hashable_view
          C = complexity (lobes + vault_refs + 1)
          D = drift + 1
        """
        # Create state with known structure
        state = create_test_state(
            lobes={"lobe_a": {"x": 1}, "lobe_b": {"y": 2}},
            vault_refs={"ref1": "v1"},
            drift=2,
        )

        # Compute components manually
        snap = state.hashable_view()
        H = shannon_entropy_counts(snap)
        C = len(json.dumps(state.lobes)) + len(state.vault_refs) + 1.0
        D = float(state.drift + 1)

        expected_se = H * C * (1.0 / D)
        actual_se = S_e_of_state(state)

        assert abs(actual_se - expected_se) < 1e-9

    def test_se_drift_effect(self):
        """
        E7 Test #9: Higher drift → lower S_e (1/D factor).
        """
        base_state = create_test_state()

        se_drift_0 = S_e_of_state(create_test_state(drift=0))
        se_drift_5 = S_e_of_state(create_test_state(drift=5))
        se_drift_10 = S_e_of_state(create_test_state(drift=10))

        # S_e should decrease as drift increases
        assert se_drift_0 > se_drift_5 > se_drift_10

    def test_se_complexity_effect(self):
        """
        E7 Test #10: More lobes/refs → higher S_e (C factor).
        """
        state_simple = create_test_state(lobes={}, vault_refs={})
        state_complex = create_test_state(
            lobes={"a": {}, "b": {}, "c": {}, "d": {}},
            vault_refs={"r1": "v1", "r2": "v2", "r3": "v3"},
        )

        se_simple = S_e_of_state(state_simple)
        se_complex = S_e_of_state(state_complex)

        # Complex state should have higher S_e
        assert se_complex > se_simple


class TestBraidFuse:
    """Test Braid.fuse() operation."""

    def test_fuse_produces_plan(self):
        """
        E7 Test #11: Fuse produces plan with required fields.
        """
        state = create_test_state()
        symbols = ["sym_abc123", "sym_def456"]

        plan = Braid.fuse(symbols, state)

        assert "symbols" in plan
        assert "routing" in plan
        assert "plan_id" in plan

    def test_fuse_routing_deterministic(self):
        """
        E7 Test #12: Routing is deterministic from soulfile_hash.
        """
        state = create_test_state(soulfile_hash=sha256_hex(b"fixed"))
        symbols = ["sym_a", "sym_b"]

        plan1 = Braid.fuse(symbols, state)
        plan2 = Braid.fuse(symbols, state)

        assert plan1["routing"] == plan2["routing"]
        assert plan1["plan_id"] == plan2["plan_id"]

    def test_fuse_different_state_different_routing(self):
        """
        E7 Test #13: Different soulfile_hash → different routing.
        """
        symbols = ["sym_test"]

        state1 = create_test_state(soulfile_hash=sha256_hex(b"soul_alpha"))
        state2 = create_test_state(soulfile_hash=sha256_hex(b"soul_beta"))

        plan1 = Braid.fuse(symbols, state1)
        plan2 = Braid.fuse(symbols, state2)

        # Routing derived from soulfile_hash should differ
        assert plan1["routing"] != plan2["routing"] or plan1["plan_id"] != plan2["plan_id"]


class TestIntegrationPath:
    """Test full symbolization → S_e → Braid path."""

    def test_full_path_deterministic(self):
        """
        E7 Test #14: Full path is deterministic.

        Same initial state + same input → same plan.
        """
        state = create_test_state()
        input_text = "recall alpha pattern"

        # Run 1
        symbols1 = KRISPER.symbolize(input_text, state)
        se1 = S_e_of_state(state)
        plan1 = Braid.fuse(symbols1, state)

        # Run 2
        symbols2 = KRISPER.symbolize(input_text, state)
        se2 = S_e_of_state(state)
        plan2 = Braid.fuse(symbols2, state)

        assert symbols1 == symbols2
        assert se1 == se2
        assert plan1 == plan2

    def test_hashable_view_excludes_timestamp(self):
        """
        E7 Test #15: hashable_view() excludes timestamp for determinism.
        """
        state1 = SELF_STATE(
            timestamp=100.0,
            soulfile_hash=sha256_hex(b"same"),
            lobes={"a": 1},
            vault_refs={"b": "2"},
            commit_ledger=[{"ts": 100.0}],
            drift=0,
        )
        state2 = SELF_STATE(
            timestamp=999.0,  # Different timestamp
            soulfile_hash=sha256_hex(b"same"),
            lobes={"a": 1},
            vault_refs={"b": "2"},
            commit_ledger=[{"ts": 999.0}],  # Different ledger
            drift=0,
        )

        # hashable_view should be identical (excludes timestamp/ledger)
        assert state1.hashable_view() == state2.hashable_view()

        # But snapshot differs (includes timestamp/ledger)
        assert state1.snapshot() != state2.snapshot()

    def test_symbolize_then_se_relationship(self):
        """
        E7 Test #16: Symbolization doesn't modify state, S_e is stable.
        """
        state = create_test_state()
        se_before = S_e_of_state(state)

        # Symbolize multiple times
        for _ in range(5):
            KRISPER.symbolize("test input", state)

        se_after = S_e_of_state(state)

        # S_e should be unchanged (symbolize is read-only)
        assert se_before == se_after


class TestShannonEntropy:
    """Test Shannon entropy helper."""

    def test_entropy_uniform_distribution(self):
        """
        E7 Test #17: Uniform distribution has high entropy.
        """
        # All possible bytes equally likely
        uniform = bytes(range(256))
        H = shannon_entropy_counts(uniform)

        # Should be close to 8 bits (max for byte data)
        assert H > 7.9

    def test_entropy_constant_data(self):
        """
        E7 Test #18: Constant data has zero entropy.
        """
        constant = b"AAAA" * 100
        H = shannon_entropy_counts(constant)

        assert H == 0.0

    def test_entropy_empty_data(self):
        """
        E7 Test #19: Empty data has zero entropy.
        """
        H = shannon_entropy_counts(b"")
        assert H == 0.0


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