"""Tests for the rolling history summarizer."""

from unittest.mock import AsyncMock

import pytest

from oclawma.providers.base import CompletionRequest, CompletionResponse, UsageStats
from oclawma.session import ConversationHistory, SessionMessage
from oclawma.session.history_summarizer import (
    FactStore,
    RollingHistorySummarizer,
    SummaryConfig,
    SummaryResult,
)


class TestSummaryConfig:
    """Test the SummaryConfig dataclass."""

    def test_default_config(self):
        """Test default configuration values."""
        config = SummaryConfig()

        assert config.keep_exchanges == 3
        assert config.trigger_threshold == 6144
        assert config.max_summary_tokens == 500
        assert config.extract_facts is True
        assert config.preserve_critical is True
        assert config.min_messages_to_summarize == 6

    def test_custom_config(self):
        """Test custom configuration."""
        config = SummaryConfig(
            keep_exchanges=5,
            trigger_threshold=4096,
            extract_facts=False,
        )

        assert config.keep_exchanges == 5
        assert config.trigger_threshold == 4096
        assert config.extract_facts is False
        assert config.max_summary_tokens == 500  # Default

    def test_from_dict(self):
        """Test creating config from dictionary."""
        data = {
            "keep_exchanges": 2,
            "trigger_threshold": 5000,
            "extract_facts": False,
        }
        config = SummaryConfig.from_dict(data)

        assert config.keep_exchanges == 2
        assert config.trigger_threshold == 5000
        assert config.extract_facts is False
        assert config.preserve_critical is True  # Default

    def test_to_dict(self):
        """Test converting config to dictionary."""
        config = SummaryConfig(keep_exchanges=2)
        data = config.to_dict()

        assert data["keep_exchanges"] == 2
        assert data["trigger_threshold"] == 6144
        assert "extract_facts" in data


class TestFactStore:
    """Test the FactStore class."""

    def test_create_empty_store(self):
        """Test creating an empty fact store."""
        store = FactStore()

        assert store.facts == []
        assert store.max_facts == 50

    def test_add_fact(self):
        """Test adding a fact."""
        store = FactStore()
        store.add_fact("Test fact", source="user", importance="high")

        assert len(store.facts) == 1
        assert store.facts[0]["fact"] == "Test fact"
        assert store.facts[0]["source"] == "user"
        assert store.facts[0]["importance"] == "high"

    def test_add_fact_defaults(self):
        """Test adding fact with default values."""
        store = FactStore()
        store.add_fact("Test fact")

        assert store.facts[0]["source"] == "summary"
        assert store.facts[0]["importance"] == "normal"

    def test_add_fact_with_metadata(self):
        """Test adding fact with metadata."""
        store = FactStore()
        store.add_fact("Test", metadata={"key": "value"})

        assert store.facts[0]["metadata"] == {"key": "value"}

    def test_max_facts_limit(self):
        """Test that facts are trimmed when exceeding max."""
        store = FactStore(max_facts=5)

        # Add 7 normal facts
        for i in range(7):
            store.add_fact(f"Fact {i}", importance="normal")

        # Should have trimmed - keeps some recent normal facts
        # Logic: keep_count = 5 - 0 - 0 = 5, normal keeps last 5//2 = 2, low keeps 5-2 = 3
        # But since all are normal, we get min(7, max(keep_count, keep_count//2)) = 3
        assert len(store.facts) <= 5
        # Should keep most recent normal facts
        assert store.facts[-1]["fact"] == "Fact 6"

    def test_critical_facts_preserved(self):
        """Test that critical facts are preserved when trimming."""
        store = FactStore(max_facts=5)

        # Add critical facts
        store.add_fact("Critical 1", importance="critical")
        store.add_fact("Critical 2", importance="critical")

        # Add normal facts
        for i in range(10):
            store.add_fact(f"Normal {i}", importance="normal")

        # Critical facts should still be there
        critical_facts = [f for f in store.facts if f["importance"] == "critical"]
        assert len(critical_facts) == 2

    def test_get_facts_filter_by_importance(self):
        """Test filtering facts by importance."""
        store = FactStore()
        store.add_fact("Critical fact", importance="critical")
        store.add_fact("Normal fact", importance="normal")

        critical = store.get_facts(importance="critical")
        assert len(critical) == 1
        assert critical[0]["fact"] == "Critical fact"

    def test_get_facts_filter_by_source(self):
        """Test filtering facts by source."""
        store = FactStore()
        store.add_fact("From user", source="user")
        store.add_fact("From summary", source="summary")

        user_facts = store.get_facts(source="user")
        assert len(user_facts) == 1
        assert user_facts[0]["fact"] == "From user"

    def test_get_critical_facts(self):
        """Test getting only critical facts as strings."""
        store = FactStore()
        store.add_fact("Critical 1", importance="critical")
        store.add_fact("Normal", importance="normal")
        store.add_fact("Critical 2", importance="critical")

        critical = store.get_critical_facts()
        assert len(critical) == 2
        assert "Critical 1" in critical
        assert "Critical 2" in critical

    def test_clear(self):
        """Test clearing all facts."""
        store = FactStore()
        store.add_fact("Test")
        store.clear()

        assert store.facts == []

    def test_to_text_empty(self):
        """Test converting empty fact store to text."""
        store = FactStore()
        text = store.to_text()

        assert text == ""

    def test_to_text_with_facts(self):
        """Test converting facts to text."""
        store = FactStore()
        store.add_fact("Fact 1", importance="critical")
        store.add_fact("Fact 2", importance="normal")

        text = store.to_text()

        assert "Key facts from previous conversation:" in text
        assert "[CRITICAL] Fact 1" in text
        assert "Fact 2" in text

    def test_to_text_without_importance(self):
        """Test converting facts to text without importance markers."""
        store = FactStore()
        store.add_fact("Fact 1", importance="critical")

        text = store.to_text(include_importance=False)

        assert "[CRITICAL]" not in text
        assert "Fact 1" in text


class TestRollingHistorySummarizer:
    """Test the RollingHistorySummarizer class."""

    def test_create_summarizer(self):
        """Test creating a summarizer."""
        config = SummaryConfig(keep_exchanges=2)
        summarizer = RollingHistorySummarizer(config=config)

        assert summarizer.config.keep_exchanges == 2
        assert summarizer.fact_store is not None
        assert summarizer._summarization_count == 0

    def test_should_summarize_too_few_messages(self):
        """Test that summarization doesn't trigger with too few messages."""
        history = ConversationHistory()
        summarizer = RollingHistorySummarizer()

        # Add only 3 messages
        for i in range(3):
            history.add_message(role="user", content=f"Message {i}")

        assert not summarizer.should_summarize(history)

    def test_should_summarize_token_threshold(self):
        """Test summarization triggers at token threshold."""
        history = ConversationHistory()
        config = SummaryConfig(
            min_messages_to_summarize=2,
            trigger_threshold=100,  # Very low threshold for testing
        )
        summarizer = RollingHistorySummarizer(config=config)

        # Add many messages to exceed token threshold
        for _i in range(10):
            history.add_message(role="user", content="X" * 200)  # ~50 tokens each

        assert summarizer.should_summarize(history)

    def test_get_messages_to_keep(self):
        """Test identifying messages to keep."""
        history = ConversationHistory()
        config = SummaryConfig(keep_exchanges=2)
        summarizer = RollingHistorySummarizer(config=config)

        # Add system message
        history.add_message(role="system", content="System")

        # Add 5 user-assistant exchanges (10 messages)
        for i in range(5):
            history.add_message(role="user", content=f"User {i}")
            history.add_message(role="assistant", content=f"Assistant {i}")

        messages = history.get_messages()
        kept = summarizer._get_messages_to_keep(messages)

        # Should keep last 2 complete exchanges (4 messages)
        assert len(kept) == 4
        assert kept[0].content == "User 3"
        assert kept[-1].content == "Assistant 4"

    def test_get_messages_to_summarize(self):
        """Test identifying messages to summarize."""
        history = ConversationHistory()
        config = SummaryConfig(keep_exchanges=1)
        summarizer = RollingHistorySummarizer(config=config)

        # Add 3 user-assistant exchanges
        for i in range(3):
            history.add_message(role="user", content=f"User {i}")
            history.add_message(role="assistant", content=f"Assistant {i}")

        messages = history.get_messages()
        to_summarize = summarizer._get_messages_to_summarize(messages)

        # Should summarize first 2 exchanges (4 messages)
        assert len(to_summarize) == 4
        assert to_summarize[0].content == "User 0"
        assert to_summarize[-1].content == "Assistant 1"

    def test_extract_critical_info(self):
        """Test extracting critical information from messages."""
        summarizer = RollingHistorySummarizer()

        messages = [
            SessionMessage(
                role="user", content="Please remember this important password: secret123"
            ),
            SessionMessage(role="assistant", content="Got it. Note: This is critical information."),
            SessionMessage(role="user", content="The API call failed with an error"),
        ]

        critical = summarizer._extract_critical_info(messages)

        assert len(critical) > 0
        # Should extract the error message
        assert any("error" in c.lower() or "failed" in c.lower() for c in critical)

    def test_generate_simple_summary(self):
        """Test simple summary generation without LLM."""
        summarizer = RollingHistorySummarizer()

        messages = [
            SessionMessage(role="user", content="Hello there"),
            SessionMessage(role="assistant", content="Hi! How can I help?"),
            SessionMessage(role="user", content="What's the weather?"),
            SessionMessage(role="assistant", content="It's sunny today."),
        ]

        summary = summarizer._generate_simple_summary(messages)

        assert "user messages" in summary
        assert "assistant responses" in summary
        assert "Hello there" in summary

    def test_format_messages_for_summary(self):
        """Test formatting messages for summary prompt."""
        summarizer = RollingHistorySummarizer()

        messages = [
            SessionMessage(role="user", content="Hello"),
            SessionMessage(role="assistant", content="Hi there"),
        ]

        formatted = summarizer._format_messages_for_summary(messages)

        assert "USER: Hello" in formatted
        assert "ASSISTANT: Hi there" in formatted

    def test_get_summary_stats(self):
        """Test getting summary statistics."""
        summarizer = RollingHistorySummarizer()
        summarizer._summarization_count = 5
        summarizer.fact_store.add_fact("Test fact")

        stats = summarizer.get_summary_stats()

        assert stats["summarization_count"] == 5
        assert stats["facts_stored"] == 1
        assert "config" in stats

    def test_reset(self):
        """Test resetting the summarizer."""
        summarizer = RollingHistorySummarizer()
        summarizer._summarization_count = 3
        summarizer.fact_store.add_fact("Test")

        summarizer.reset()

        assert summarizer._summarization_count == 0
        assert len(summarizer.fact_store.facts) == 0


class TestSummaryResult:
    """Test the SummaryResult dataclass."""

    def test_create_result(self):
        """Test creating a summary result."""
        result = SummaryResult(
            summary="Test summary",
            facts=["Fact 1", "Fact 2"],
            messages_summarized=10,
            tokens_saved=500,
        )

        assert result.summary == "Test summary"
        assert len(result.facts) == 2
        assert result.tokens_saved == 500

    def test_compression_ratio(self):
        """Test compression ratio calculation."""
        result = SummaryResult(
            summary="Summary",
            original_tokens=1000,
            new_tokens=200,
        )

        assert result.compression_ratio == 0.2

    def test_compression_ratio_zero(self):
        """Test compression ratio with zero original tokens."""
        result = SummaryResult(summary="Summary", original_tokens=0)

        assert result.compression_ratio == 1.0


class TestSummarizerIntegration:
    """Integration tests for the summarizer with mock provider."""

    @pytest.mark.asyncio
    async def test_summarize_with_mock_provider(self):
        """Test summarization with a mock LLM provider."""
        # Create mock provider
        mock_provider = AsyncMock()
        mock_response = CompletionResponse(
            content="Summary of conversation. FACT: Key fact extracted.",
            model="test-model",
            usage=UsageStats(100, 50, 150),
        )
        mock_provider.complete.return_value = mock_response

        # Create history
        history = ConversationHistory()
        history.add_message(role="system", content="System prompt")

        for i in range(6):
            history.add_message(role="user", content=f"Question {i}")
            history.add_message(role="assistant", content=f"Answer {i}")

        # Create summarizer with mock
        config = SummaryConfig(keep_exchanges=2, extract_facts=True)
        summarizer = RollingHistorySummarizer(config=config)

        # Run summarization
        result = await summarizer.summarize(history, provider=mock_provider, model="test-model")

        # Verify result
        assert result.summary == "Summary of conversation."
        assert len(result.facts) == 1
        assert result.facts[0] == "Key fact extracted."
        assert result.messages_summarized == 8  # 6 user + 6 assistant - 2 kept exchanges

        # Verify provider was called
        mock_provider.complete.assert_called_once()
        call_args = mock_provider.complete.call_args[0][0]
        assert isinstance(call_args, CompletionRequest)
        assert call_args.model == "test-model"

    @pytest.mark.asyncio
    async def test_summarize_preserves_system_message(self):
        """Test that system message is preserved after summarization."""
        mock_provider = AsyncMock()
        mock_provider.complete.return_value = CompletionResponse(
            content="Summary",
            model="test",
            usage=UsageStats(10, 10, 20),
        )

        history = ConversationHistory()
        history.add_message(role="system", content="Important system prompt")
        history.add_message(role="user", content="Hello")
        history.add_message(role="assistant", content="Hi")
        history.add_message(role="user", content="How are you?")
        history.add_message(role="assistant", content="I'm fine")

        summarizer = RollingHistorySummarizer(config=SummaryConfig(keep_exchanges=1))

        await summarizer.summarize(history, provider=mock_provider)

        # Check system message is preserved
        messages = history.get_messages()
        system_msgs = [m for m in messages if m.role == "system"]
        assert len(system_msgs) == 1
        assert system_msgs[0].content == "Important system prompt"

    @pytest.mark.asyncio
    async def test_summarize_handles_provider_error(self):
        """Test that summarization handles provider errors gracefully."""
        mock_provider = AsyncMock()
        mock_provider.complete.side_effect = Exception("Provider error")

        history = ConversationHistory()
        history.add_message(role="user", content="Hello")
        history.add_message(role="assistant", content="Hi")
        history.add_message(role="user", content="Test")
        history.add_message(role="assistant", content="Response")

        summarizer = RollingHistorySummarizer(config=SummaryConfig(keep_exchanges=1))

        # Should not raise, should fall back to simple summary
        result = await summarizer.summarize(history, provider=mock_provider)

        assert result.summary != ""
        assert result.facts == []  # No facts extracted on error

    @pytest.mark.asyncio
    async def test_summarize_without_provider(self):
        """Test summarization without a provider (simple summary mode)."""
        history = ConversationHistory()

        for i in range(6):
            history.add_message(role="user", content=f"Question {i}")
            history.add_message(role="assistant", content=f"Answer {i}")

        summarizer = RollingHistorySummarizer(config=SummaryConfig(keep_exchanges=2))

        result = await summarizer.summarize(history)

        # Should generate simple summary without LLM
        assert "user messages" in result.summary
        assert result.facts == []
