"""Tests for context compression suggestions."""

import pytest

from oclawma.context.budget import BudgetThreshold, ContextBudget
from oclawma.context.compression import (
    CompressionAction,
    CompressionPlan,
    CompressionSuggestion,
    ContextCompressionSuggestions,
    TokenBreakdown,
    create_compression_suggestions,
)


class TestTokenBreakdown:
    """Tests for TokenBreakdown."""

    def test_empty_breakdown(self):
        """Test empty token breakdown."""
        breakdown = TokenBreakdown()
        assert breakdown.total == 0
        assert breakdown.to_dict()["total"] == 0

    def test_breakdown_total(self):
        """Test breakdown total calculation."""
        breakdown = TokenBreakdown(
            system_prompt=500,
            conversation_history=2000,
            file_contents=1000,
            tool_results=500,
            attachments=200,
            other=100,
        )
        assert breakdown.total == 4300

    def test_breakdown_to_dict(self):
        """Test breakdown to_dict."""
        breakdown = TokenBreakdown(
            system_prompt=500,
            conversation_history=2000,
        )
        data = breakdown.to_dict()
        assert data["system_prompt"] == 500
        assert data["conversation_history"] == 2000
        assert data["total"] == 2500

    def test_breakdown_visualize(self):
        """Test breakdown visualization."""
        breakdown = TokenBreakdown(
            system_prompt=500,
            conversation_history=2000,
            file_contents=1000,
        )
        viz = breakdown.visualize(budget=8192)
        assert "System Prompt" in viz
        assert "Conversation" in viz
        assert "Files" in viz
        assert "tokens" in viz

    def test_breakdown_visualize_empty(self):
        """Test visualization with empty breakdown."""
        breakdown = TokenBreakdown()
        viz = breakdown.visualize(budget=8192)
        # Should not crash, just show total
        assert "Total" in viz


class TestCompressionSuggestion:
    """Tests for CompressionSuggestion."""

    def test_suggestion_creation(self):
        """Test creating a compression suggestion."""
        suggestion = CompressionSuggestion(
            action=CompressionAction.SUMMARIZE,
            title="Test Suggestion",
            description="Test description",
            estimated_savings=1000,
            priority=5,
            command="/test",
            auto_executable=True,
        )
        assert suggestion.action == CompressionAction.SUMMARIZE
        assert suggestion.title == "Test Suggestion"
        assert suggestion.estimated_savings == 1000
        assert suggestion.auto_executable is True

    def test_suggestion_to_dict(self):
        """Test suggestion to_dict."""
        suggestion = CompressionSuggestion(
            action=CompressionAction.COMPACT,
            title="Compact",
            description="Compact description",
            estimated_savings=2000,
            command="oclawma compact",
        )
        data = suggestion.to_dict()
        assert data["action"] == "compact"
        assert data["title"] == "Compact"
        assert data["estimated_savings"] == 2000
        assert data["command"] == "oclawma compact"


class TestCompressionPlan:
    """Tests for CompressionPlan."""

    def test_empty_plan(self):
        """Test empty compression plan."""
        plan = CompressionPlan(
            current_usage=4000,
            budget=8192,
            usage_percent=48.8,
            threshold=BudgetThreshold.NORMAL,
        )
        assert plan.total_potential_savings == 0
        assert plan.projected_usage == 4000
        assert plan.get_executable_suggestions() == []

    def test_plan_with_suggestions(self):
        """Test plan with suggestions."""
        suggestions = [
            CompressionSuggestion(
                action=CompressionAction.SUMMARIZE,
                title="Summarize",
                description="Desc",
                estimated_savings=1000,
                auto_executable=True,
            ),
            CompressionSuggestion(
                action=CompressionAction.RESET,
                title="Reset",
                description="Desc",
                estimated_savings=4000,
                auto_executable=False,
            ),
        ]
        plan = CompressionPlan(
            current_usage=7000,
            budget=8192,
            usage_percent=85.4,
            threshold=BudgetThreshold.CRITICAL,
            suggestions=suggestions,
        )
        assert plan.total_potential_savings == 5000
        assert plan.projected_usage == 2000
        assert plan.projected_percent == pytest.approx(24.4, rel=0.1)
        assert len(plan.get_executable_suggestions()) == 1

    def test_plan_to_dict(self):
        """Test plan to_dict."""
        plan = CompressionPlan(
            current_usage=6000,
            budget=8192,
            usage_percent=73.2,
            threshold=BudgetThreshold.WARNING,
            suggestions=[],
        )
        data = plan.to_dict()
        assert data["current_usage"] == 6000
        assert data["budget"] == 8192
        assert data["threshold"] == "warning"
        assert data["total_potential_savings"] == 0


class TestContextCompressionSuggestions:
    """Tests for ContextCompressionSuggestions."""

    def test_initialization(self):
        """Test initialization."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)
        assert suggester.budget == budget
        assert suggester._history == []

    def test_analyze_normal_threshold(self):
        """Test analyze with normal threshold."""
        budget = ContextBudget()
        budget.allocate(1000, strict=False)  # Well below warning threshold
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()

        assert plan.threshold == BudgetThreshold.NORMAL
        assert len(plan.suggestions) == 0
        assert plan.message is None

    def test_analyze_warning_threshold(self):
        """Test analyze with warning threshold."""
        budget = ContextBudget()
        budget.allocate(6500, strict=False)  # Above 75% warning
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()

        assert plan.threshold == BudgetThreshold.WARNING
        assert len(plan.suggestions) > 0
        assert plan.message is not None
        assert "75%" in plan.message or "79%" in plan.message or "80%" in plan.message

    def test_analyze_critical_threshold(self):
        """Test analyze with critical threshold."""
        budget = ContextBudget()
        budget.allocate(7300, strict=False)  # Above 87% critical
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()

        assert plan.threshold == BudgetThreshold.CRITICAL
        assert len(plan.suggestions) > 0
        assert plan.message is not None
        assert "🚨" in plan.message

    def test_analyze_exhausted_threshold(self):
        """Test analyze with exhausted threshold."""
        budget = ContextBudget()
        budget.allocate(8200, strict=False)  # At 100%
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()

        assert plan.threshold == BudgetThreshold.EXHAUSTED
        assert plan.message is not None
        assert "🛑" in plan.message

    def test_should_suggest_compression(self):
        """Test should_suggest_compression detection."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        # Below warning - should not suggest
        budget.reset()
        budget.allocate(5000, strict=False)
        assert not suggester.should_suggest_compression()

        # At warning - should suggest
        budget.reset()
        budget.allocate(6200, strict=False)
        assert suggester.should_suggest_compression()

        # At critical - should suggest
        budget.reset()
        budget.allocate(7200, strict=False)
        assert suggester.should_suggest_compression(BudgetThreshold.WARNING)

    def test_should_suggest_with_threshold(self):
        """Test should_suggest_compression with explicit threshold."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        # At warning level
        budget.reset()
        budget.allocate(6200, strict=False)

        # Should suggest if min threshold is WARNING
        assert suggester.should_suggest_compression(BudgetThreshold.WARNING)
        # Should not suggest if min threshold is CRITICAL
        assert not suggester.should_suggest_compression(BudgetThreshold.CRITICAL)

    def test_suggestions_priority_ordering(self):
        """Test that suggestions are ordered by priority."""
        budget = ContextBudget()
        budget.allocate(8000, strict=False)  # High usage
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()

        # Suggestions should be sorted by priority (high to low)
        priorities = [s.priority for s in plan.suggestions]
        assert priorities == sorted(priorities, reverse=True)

    def test_format_suggestions(self):
        """Test format_suggestions output."""
        budget = ContextBudget()
        budget.allocate(6500, strict=False)
        suggester = ContextCompressionSuggestions(budget=budget)

        plan = suggester.analyze()
        formatted = suggester.format_suggestions(plan)

        assert "CONTEXT" in formatted
        assert "Current usage" in formatted
        assert "tokens" in formatted

    def test_format_suggestions_verbose(self):
        """Test format_suggestions with verbose=True."""
        budget = ContextBudget()
        budget.allocate(6500, strict=False)
        suggester = ContextCompressionSuggestions(budget=budget)

        breakdown = TokenBreakdown(
            system_prompt=500,
            conversation_history=3000,
            file_contents=2000,
        )
        plan = suggester.analyze(breakdown=breakdown)
        formatted = suggester.format_suggestions(plan, verbose=True)

        assert "BREAKDOWN" in formatted or "System Prompt" in formatted
        assert "SUGGESTED" in formatted or "Actions" in formatted

    def test_get_history(self):
        """Test get_history tracks suggestions."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        # Initially empty
        assert suggester.get_history() == []

        # After analyze
        budget.allocate(6500, strict=False)
        suggester.analyze()

        history = suggester.get_history()
        assert len(history) == 1
        assert "timestamp" in history[0]
        assert "usage" in history[0]
        assert "suggestions_count" in history[0]

    def test_calculate_breakdown(self):
        """Test _calculate_breakdown method."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        # Mock history object
        class MockMessage:
            def __init__(self, role, content, metadata=None):
                self.role = role
                self.content = content
                self.metadata = metadata or {}

        class MockHistory:
            def get_messages(self):
                return [
                    MockMessage("system", "System prompt" * 50),  # ~500 chars = ~125 tokens
                    MockMessage("user", "User message" * 100),  # ~1200 chars = ~300 tokens
                    MockMessage(
                        "assistant", "Assistant response" * 100
                    ),  # ~1800 chars = ~450 tokens
                ]

        history = MockHistory()
        breakdown = suggester._calculate_breakdown(history)

        assert breakdown.system_prompt > 0
        assert breakdown.conversation_history > 0
        assert breakdown.total > 0

    def test_estimate_summarization_savings(self):
        """Test _estimate_summarization_savings."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        # Mock history with many messages
        class MockMessage:
            def __init__(self, role, content):
                self.role = role
                self.content = content

        class MockHistory:
            def get_messages(self):
                return [MockMessage("user", "x" * 1000) for _ in range(10)]

        history = MockHistory()
        savings = suggester._estimate_summarization_savings(history)

        assert savings > 0

    def test_estimate_summarization_savings_short_history(self):
        """Test savings estimation with short history."""
        budget = ContextBudget()
        suggester = ContextCompressionSuggestions(budget=budget)

        class MockMessage:
            def __init__(self, role, content):
                self.role = role
                self.content = content

        class MockHistory:
            def get_messages(self):
                return [MockMessage("user", "x" * 100) for _ in range(2)]

        history = MockHistory()
        savings = suggester._estimate_summarization_savings(history)

        assert savings == 0  # Too short to summarize


class TestCreateCompressionSuggestions:
    """Tests for create_compression_suggestions convenience function."""

    def test_create_compression_suggestions(self):
        """Test convenience function."""
        budget = ContextBudget()
        budget.allocate(6500, strict=False)

        plan = create_compression_suggestions(budget=budget)

        assert isinstance(plan, CompressionPlan)
        assert plan.current_usage == 6500

    def test_create_compression_suggestions_no_budget(self):
        """Test with default budget."""
        plan = create_compression_suggestions()

        assert isinstance(plan, CompressionPlan)
        assert plan.budget == 8192  # Default budget


class TestCompressionActions:
    """Tests for CompressionAction enum."""

    def test_action_values(self):
        """Test action enum values."""
        assert CompressionAction.DROP_FILES.value == "drop_files"
        assert CompressionAction.SUMMARIZE.value == "summarize"
        assert CompressionAction.ARCHIVE.value == "archive"
        assert CompressionAction.CLEAR_ATTACHMENTS.value == "clear_attachments"
        assert CompressionAction.RESET.value == "reset"
        assert CompressionAction.COMPACT.value == "compact"
