"""
Kill switch tests for Hippo LCM.

Verifies that `config.enabled = False` and `config.autocompact_disabled = True`
correctly gate compaction and after_turn behavior.

Issue: #1125
"""

from __future__ import annotations

import pytest
import pytest_asyncio

from cortex.hippo.config import HippoConfig
from cortex.hippo.engine import HippoEngine
from cortex.hippo.models import CompactionResult, ContextItemType
from cortex.hippo.summarize import SummarizeResult


# ═══════════════════════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════════════════════


async def _mock_summarize(text: str, aggressive: bool, options=None) -> SummarizeResult:
    return SummarizeResult(
        content=f"Summary of {len(text)} chars.",
        quality_score=0.8,
        quality_breakdown={"compression": 0.8, "coherence": 0.8, "coverage": 0.8},
        source="content",
        mode_used="aggressive" if aggressive else "normal",
    )


async def _ingest_messages(engine: HippoEngine, n: int = 10) -> None:
    for i in range(n):
        role = "user" if i % 2 == 0 else "assistant"
        await engine.ingest_message(role, f"Message {i}: " + "x" * 80)


def _disabled_config(**overrides) -> HippoConfig:
    """Base config with Hippo disabled."""
    defaults = dict(
        enabled=False,
        db_path=":memory:",
        fresh_tail_count=4,
        max_tokens=10_000,
        context_threshold=0.3,
        context_window_tokens=100,
        leaf_trigger_margin=10,
        leaf_chunk_tokens=200,
        leaf_min_fanout=2,
        condensed_min_fanout=2,
        condensed_min_fanout_hard=2,
        condensed_min_chunk_tokens=20,
        fallback_max_tokens=128,
    )
    defaults.update(overrides)
    return HippoConfig(**defaults)


def _enabled_config(**overrides) -> HippoConfig:
    """Base config with Hippo enabled."""
    return _disabled_config(enabled=True, **overrides)


# ═══════════════════════════════════════════════════════════════════════════
# Test: config.enabled = False prevents compaction
# ═══════════════════════════════════════════════════════════════════════════


class TestEnabledFalse:
    """When config.enabled = False, compaction and after_turn are blocked."""

    @pytest.mark.asyncio
    async def test_compact_returns_noop_when_disabled(self):
        """compact() returns empty result when enabled=False."""
        config = _disabled_config()
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="kill-1")

        try:
            await _ingest_messages(engine, 10)

            result = await engine.compact(
                mode="full",
                token_budget=config.max_tokens,
                summarize_fn=_mock_summarize,
            )

            assert result.summaries_created == 0
            assert result.messages_compacted == 0

            stats = await engine.get_stats()
            assert stats.summary_count == 0
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_after_turn_skips_when_disabled(self):
        """after_turn does NOT ingest or compact when enabled=False."""
        config = _disabled_config()
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        conv = await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="kill-2")

        try:
            messages = [
                {"role": "user", "content": f"Message {i}: " + "x" * 80}
                for i in range(10)
            ]

            await engine.after_turn(
                conversation_id=conv.conversation_id,
                messages=messages,
                pre_prompt_count=0,
                token_budget=config.max_tokens,
            )

            stats = await engine.get_stats()
            # after_turn should have skipped entirely — no ingestion
            assert stats.message_count == 0
            assert stats.summary_count == 0
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_assembly_works_as_passthrough_when_disabled(self):
        """Assembly still works when enabled=False — returns raw messages."""
        config = _disabled_config()
        engine = HippoEngine(config=config)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="kill-3")

        try:
            # Ingest directly (bypass after_turn which is gated)
            await _ingest_messages(engine, 5)

            result = await engine.get_context()
            assert len(result.messages) == 5
            assert result.summary_tokens == 0
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_get_context_works_when_disabled(self):
        """get_context works even when Hippo is disabled."""
        config = _disabled_config()
        engine = HippoEngine(config=config)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="kill-4")

        try:
            # Empty context should not error
            result = await engine.get_context()
            assert result.total_tokens == 0
            assert result.messages == []
        finally:
            await engine.shutdown()


# ═══════════════════════════════════════════════════════════════════════════
# Test: Toggling enabled mid-session
# ═══════════════════════════════════════════════════════════════════════════


class TestToggleEnabled:
    """Toggling config.enabled mid-session works correctly."""

    @pytest.mark.asyncio
    async def test_toggle_on_allows_compaction(self):
        """Start disabled, toggle to enabled, compaction then works."""
        config = _disabled_config()
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="toggle-1")

        try:
            await _ingest_messages(engine, 10)

            # Disabled — compaction blocked
            result = await engine.compact(mode="full", summarize_fn=_mock_summarize)
            assert result.summaries_created == 0

            # Toggle ON
            engine.config = engine.config.model_copy(update={"enabled": True})

            # Now compaction should work
            result = await engine.compact(mode="full", summarize_fn=_mock_summarize)
            # May or may not create summaries depending on threshold, but should not be blocked
            # The key is it doesn't return early due to kill switch
            stats = await engine.get_stats()
            # At minimum, the function attempted to run (not blocked)
            assert isinstance(result.summaries_created, int)
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_toggle_off_blocks_compaction(self):
        """Start enabled, toggle to disabled, compaction then blocked."""
        config = _enabled_config()
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="toggle-2")

        try:
            await _ingest_messages(engine, 10)

            # Enabled — compaction would work
            # Toggle OFF
            engine.config = engine.config.model_copy(update={"enabled": False})

            result = await engine.compact(mode="full", summarize_fn=_mock_summarize)
            assert result.summaries_created == 0

            stats = await engine.get_stats()
            assert stats.summary_count == 0
        finally:
            await engine.shutdown()


# ═══════════════════════════════════════════════════════════════════════════
# Test: autocompact_disabled blocks compaction but allows other ops
# ═══════════════════════════════════════════════════════════════════════════


class TestAutocompactDisabled:
    """autocompact_disabled blocks compaction but allows ingest/assembly."""

    @pytest.mark.asyncio
    async def test_autocompact_disabled_blocks_compact(self):
        """compact() returns no-op when autocompact_disabled=True."""
        config = _enabled_config(autocompact_disabled=True)
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="autocompact-1")

        try:
            await _ingest_messages(engine, 10)

            result = await engine.compact(mode="full", summarize_fn=_mock_summarize)
            assert result.summaries_created == 0

            stats = await engine.get_stats()
            assert stats.summary_count == 0
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_autocompact_disabled_allows_ingest(self):
        """Ingestion still works when autocompact_disabled=True."""
        config = _enabled_config(autocompact_disabled=True)
        engine = HippoEngine(config=config)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="autocompact-2")

        try:
            await _ingest_messages(engine, 5)

            stats = await engine.get_stats()
            assert stats.message_count == 5
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_autocompact_disabled_allows_assembly(self):
        """Assembly still works when autocompact_disabled=True."""
        config = _enabled_config(autocompact_disabled=True)
        engine = HippoEngine(config=config)
        await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="autocompact-3")

        try:
            await _ingest_messages(engine, 5)

            result = await engine.get_context()
            assert len(result.messages) == 5
            assert result.total_tokens > 0
        finally:
            await engine.shutdown()

    @pytest.mark.asyncio
    async def test_autocompact_disabled_blocks_after_turn_compaction(self):
        """after_turn ingests but does NOT trigger compaction when autocompact_disabled=True."""
        config = _enabled_config(
            autocompact_disabled=True,
            context_window_tokens=100,
            leaf_trigger_margin=10,
        )
        engine = HippoEngine(config=config, summarize_fn=_mock_summarize)
        conv = await engine.initialize(db_path=":memory:", owner_id="test", conversation_id="autocompact-4")

        try:
            messages = [
                {"role": "user" if i % 2 == 0 else "assistant", "content": f"Msg {i}: " + "x" * 80}
                for i in range(15)
            ]

            await engine.after_turn(
                conversation_id=conv.conversation_id,
                messages=messages,
                pre_prompt_count=0,
                token_budget=config.max_tokens,
            )

            import asyncio
            await asyncio.sleep(0.1)  # Let any background tasks settle

            stats = await engine.get_stats()
            # Messages should be ingested
            assert stats.message_count >= 10
            # But no compaction should have occurred
            assert stats.summary_count == 0
        finally:
            await engine.shutdown()


# ═══════════════════════════════════════════════════════════════════════════
# Test: Config field existence
# ═══════════════════════════════════════════════════════════════════════════


class TestKillSwitchConfig:
    """Verify kill switch configuration fields exist and have correct defaults."""

    def test_enabled_field_exists(self):
        """HippoConfig has `enabled` field."""
        config = HippoConfig()
        assert hasattr(config, "enabled")
        assert isinstance(config.enabled, bool)

    def test_enabled_defaults_false(self):
        """enabled defaults to False (safe default)."""
        config = HippoConfig()
        assert config.enabled is False

    def test_autocompact_disabled_field_exists(self):
        """HippoConfig has `autocompact_disabled` field."""
        config = HippoConfig()
        assert hasattr(config, "autocompact_disabled")
        assert isinstance(config.autocompact_disabled, bool)

    def test_autocompact_disabled_defaults_false(self):
        """autocompact_disabled defaults to False."""
        config = HippoConfig()
        assert config.autocompact_disabled is False

    def test_enabled_can_be_set_true(self):
        """enabled can be explicitly set to True."""
        config = HippoConfig(enabled=True)
        assert config.enabled is True

    def test_config_model_copy_preserves_enabled(self):
        """model_copy preserves enabled state."""
        config = HippoConfig(enabled=True)
        copy = config.model_copy(update={"max_tokens": 50_000})
        assert copy.enabled is True
        assert copy.max_tokens == 50_000
