"""Tests for context types."""

from datetime import UTC, datetime, timedelta

import pytest

from app.services.context.types import (
    AssembledContext,
    ContextPriority,
    ContextType,
    ConversationContext,
    KnowledgeContext,
    MessageRole,
    SystemContext,
    TaskContext,
    TaskStatus,
    ToolContext,
    ToolResultStatus,
)


class TestContextType:
    """Tests for ContextType enum."""

    def test_all_types_exist(self) -> None:
        """Test that all expected context types exist."""
        assert ContextType.SYSTEM
        assert ContextType.TASK
        assert ContextType.KNOWLEDGE
        assert ContextType.CONVERSATION
        assert ContextType.TOOL

    def test_from_string_valid(self) -> None:
        """Test from_string with valid values."""
        assert ContextType.from_string("system") == ContextType.SYSTEM
        assert ContextType.from_string("KNOWLEDGE") == ContextType.KNOWLEDGE
        assert ContextType.from_string("Task") == ContextType.TASK

    def test_from_string_invalid(self) -> None:
        """Test from_string with invalid value."""
        with pytest.raises(ValueError) as exc_info:
            ContextType.from_string("invalid")
        assert "Invalid context type" in str(exc_info.value)


class TestContextPriority:
    """Tests for ContextPriority enum."""

    def test_priority_ordering(self) -> None:
        """Test that priorities are ordered correctly."""
        assert ContextPriority.LOWEST.value < ContextPriority.LOW.value
        assert ContextPriority.LOW.value < ContextPriority.NORMAL.value
        assert ContextPriority.NORMAL.value < ContextPriority.HIGH.value
        assert ContextPriority.HIGH.value < ContextPriority.HIGHEST.value
        assert ContextPriority.HIGHEST.value < ContextPriority.CRITICAL.value

    def test_from_int(self) -> None:
        """Test from_int conversion."""
        assert ContextPriority.from_int(0) == ContextPriority.LOWEST
        assert ContextPriority.from_int(50) == ContextPriority.NORMAL
        assert ContextPriority.from_int(100) == ContextPriority.HIGHEST
        assert ContextPriority.from_int(200) == ContextPriority.CRITICAL

    def test_from_int_intermediate(self) -> None:
        """Test from_int with intermediate values."""
        # Should return closest lower priority
        assert ContextPriority.from_int(30) == ContextPriority.LOW
        assert ContextPriority.from_int(60) == ContextPriority.NORMAL


class TestSystemContext:
    """Tests for SystemContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = SystemContext(
            content="You are a helpful assistant.",
            source="system_prompt",
        )

        assert ctx.content == "You are a helpful assistant."
        assert ctx.source == "system_prompt"
        assert ctx.get_type() == ContextType.SYSTEM

    def test_default_high_priority(self) -> None:
        """Test that system context defaults to high priority."""
        ctx = SystemContext(content="Test", source="test")
        assert ctx.priority == ContextPriority.HIGH.value

    def test_create_persona(self) -> None:
        """Test create_persona factory method."""
        ctx = SystemContext.create_persona(
            name="Code Assistant",
            description="A helpful coding assistant.",
            capabilities=["Write code", "Debug"],
            constraints=["Never expose secrets"],
        )

        assert "Code Assistant" in ctx.content
        assert "helpful coding assistant" in ctx.content
        assert "Write code" in ctx.content
        assert "Never expose secrets" in ctx.content
        assert ctx.priority == ContextPriority.HIGHEST.value

    def test_create_instructions(self) -> None:
        """Test create_instructions factory method."""
        ctx = SystemContext.create_instructions(
            ["Always be helpful", "Be concise"],
            source="rules",
        )

        assert "Always be helpful" in ctx.content
        assert "Be concise" in ctx.content

    def test_to_dict(self) -> None:
        """Test serialization to dict."""
        ctx = SystemContext(
            content="Test",
            source="test",
            role="assistant",
            instructions_type="general",
        )

        data = ctx.to_dict()
        assert data["role"] == "assistant"
        assert data["instructions_type"] == "general"
        assert data["type"] == "system"


class TestKnowledgeContext:
    """Tests for KnowledgeContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = KnowledgeContext(
            content="def authenticate(user): ...",
            source="/src/auth.py",
            collection="code",
            file_type="python",
        )

        assert ctx.content == "def authenticate(user): ..."
        assert ctx.source == "/src/auth.py"
        assert ctx.collection == "code"
        assert ctx.get_type() == ContextType.KNOWLEDGE

    def test_from_search_result(self) -> None:
        """Test from_search_result factory method."""
        result = {
            "content": "Test content",
            "source_path": "/test/file.py",
            "collection": "code",
            "file_type": "python",
            "chunk_index": 2,
            "score": 0.85,
            "id": "chunk-123",
        }

        ctx = KnowledgeContext.from_search_result(result, "test query")

        assert ctx.content == "Test content"
        assert ctx.source == "/test/file.py"
        assert ctx.relevance_score == 0.85
        assert ctx.search_query == "test query"

    def test_from_search_results(self) -> None:
        """Test from_search_results factory method."""
        results = [
            {"content": "Content 1", "source_path": "/a.py", "score": 0.9},
            {"content": "Content 2", "source_path": "/b.py", "score": 0.8},
        ]

        contexts = KnowledgeContext.from_search_results(results, "query")

        assert len(contexts) == 2
        assert contexts[0].relevance_score == 0.9
        assert contexts[1].source == "/b.py"

    def test_is_code(self) -> None:
        """Test is_code method."""
        code_ctx = KnowledgeContext(content="code", source="test", file_type="python")
        doc_ctx = KnowledgeContext(content="docs", source="test", file_type="markdown")

        assert code_ctx.is_code() is True
        assert doc_ctx.is_code() is False

    def test_is_documentation(self) -> None:
        """Test is_documentation method."""
        doc_ctx = KnowledgeContext(content="docs", source="test", file_type="markdown")
        code_ctx = KnowledgeContext(content="code", source="test", file_type="python")

        assert doc_ctx.is_documentation() is True
        assert code_ctx.is_documentation() is False


class TestConversationContext:
    """Tests for ConversationContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = ConversationContext(
            content="Hello, how can I help?",
            source="conversation",
            role=MessageRole.ASSISTANT,
            turn_index=1,
        )

        assert ctx.content == "Hello, how can I help?"
        assert ctx.role == MessageRole.ASSISTANT
        assert ctx.get_type() == ContextType.CONVERSATION

    def test_from_message(self) -> None:
        """Test from_message factory method."""
        ctx = ConversationContext.from_message(
            content="What is Python?",
            role="user",
            turn_index=0,
        )

        assert ctx.content == "What is Python?"
        assert ctx.role == MessageRole.USER
        assert ctx.turn_index == 0

    def test_from_history(self) -> None:
        """Test from_history factory method."""
        messages = [
            {"role": "user", "content": "Hello"},
            {"role": "assistant", "content": "Hi there!"},
            {"role": "user", "content": "Help me"},
        ]

        contexts = ConversationContext.from_history(messages)

        assert len(contexts) == 3
        assert contexts[0].role == MessageRole.USER
        assert contexts[1].role == MessageRole.ASSISTANT
        assert contexts[2].turn_index == 2

    def test_is_user_message(self) -> None:
        """Test is_user_message method."""
        user_ctx = ConversationContext(
            content="test", source="test", role=MessageRole.USER
        )
        assistant_ctx = ConversationContext(
            content="test", source="test", role=MessageRole.ASSISTANT
        )

        assert user_ctx.is_user_message() is True
        assert assistant_ctx.is_user_message() is False

    def test_format_for_prompt(self) -> None:
        """Test format_for_prompt method."""
        ctx = ConversationContext.from_message(
            content="What is 2+2?",
            role="user",
        )

        formatted = ctx.format_for_prompt()
        assert "User:" in formatted
        assert "What is 2+2?" in formatted


class TestTaskContext:
    """Tests for TaskContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = TaskContext(
            content="Implement login feature",
            source="task",
            title="Login Feature",
        )

        assert ctx.content == "Implement login feature"
        assert ctx.title == "Login Feature"
        assert ctx.get_type() == ContextType.TASK

    def test_default_normal_priority(self) -> None:
        """Test that task context uses NORMAL priority from base class."""
        ctx = TaskContext(content="Test", source="test")
        # TaskContext inherits NORMAL priority from BaseContext
        # Use TaskContext.create() for default HIGH priority behavior
        assert ctx.priority == ContextPriority.NORMAL.value

    def test_explicit_high_priority(self) -> None:
        """Test setting explicit HIGH priority."""
        ctx = TaskContext(
            content="Test",
            source="test",
            priority=ContextPriority.HIGH.value,
        )
        assert ctx.priority == ContextPriority.HIGH.value

    def test_create_factory(self) -> None:
        """Test create factory method."""
        ctx = TaskContext.create(
            title="Add Auth",
            description="Implement authentication",
            acceptance_criteria=["Tests pass", "Code reviewed"],
            constraints=["Use JWT"],
            issue_id="123",
        )

        assert ctx.title == "Add Auth"
        assert ctx.content == "Implement authentication"
        assert len(ctx.acceptance_criteria) == 2
        assert "Use JWT" in ctx.constraints
        assert ctx.status == TaskStatus.IN_PROGRESS

    def test_format_for_prompt(self) -> None:
        """Test format_for_prompt method."""
        ctx = TaskContext.create(
            title="Test Task",
            description="Do something",
            acceptance_criteria=["Works correctly"],
        )

        formatted = ctx.format_for_prompt()
        assert "Task: Test Task" in formatted
        assert "Do something" in formatted
        assert "Works correctly" in formatted

    def test_status_checks(self) -> None:
        """Test status check methods."""
        pending = TaskContext(content="test", source="test", status=TaskStatus.PENDING)
        completed = TaskContext(
            content="test", source="test", status=TaskStatus.COMPLETED
        )
        blocked = TaskContext(content="test", source="test", status=TaskStatus.BLOCKED)

        assert pending.is_active() is True
        assert completed.is_complete() is True
        assert blocked.is_blocked() is True


class TestToolContext:
    """Tests for ToolContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = ToolContext(
            content="Tool result here",
            source="tool:search",
            tool_name="search",
        )

        assert ctx.tool_name == "search"
        assert ctx.get_type() == ContextType.TOOL

    def test_from_tool_definition(self) -> None:
        """Test from_tool_definition factory method."""
        ctx = ToolContext.from_tool_definition(
            name="search_knowledge",
            description="Search the knowledge base",
            parameters={
                "query": {"type": "string", "required": True},
                "limit": {"type": "integer", "required": False},
            },
            server_name="knowledge-base",
        )

        assert ctx.tool_name == "search_knowledge"
        assert "Search the knowledge base" in ctx.content
        assert ctx.is_result is False
        assert ctx.server_name == "knowledge-base"

    def test_from_tool_result(self) -> None:
        """Test from_tool_result factory method."""
        ctx = ToolContext.from_tool_result(
            tool_name="search",
            result={"found": 5, "items": ["a", "b"]},
            status=ToolResultStatus.SUCCESS,
            execution_time_ms=150.5,
        )

        assert ctx.tool_name == "search"
        assert ctx.is_result is True
        assert ctx.result_status == ToolResultStatus.SUCCESS
        assert "found" in ctx.content

    def test_is_successful(self) -> None:
        """Test is_successful method."""
        success = ToolContext.from_tool_result("test", "ok", ToolResultStatus.SUCCESS)
        error = ToolContext.from_tool_result("test", "error", ToolResultStatus.ERROR)

        assert success.is_successful() is True
        assert error.is_successful() is False

    def test_format_for_prompt(self) -> None:
        """Test format_for_prompt method."""
        ctx = ToolContext.from_tool_result(
            "search",
            "Found 3 results",
            ToolResultStatus.SUCCESS,
        )

        formatted = ctx.format_for_prompt()
        assert "Tool Result" in formatted
        assert "search" in formatted
        assert "success" in formatted


class TestAssembledContext:
    """Tests for AssembledContext."""

    def test_creation(self) -> None:
        """Test basic creation."""
        ctx = AssembledContext(
            content="Assembled content here",
            total_tokens=500,
            context_count=5,
        )

        assert ctx.content == "Assembled content here"
        assert ctx.total_tokens == 500
        assert ctx.context_count == 5
        # Test backward compatibility aliases
        assert ctx.token_count == 500
        assert ctx.contexts_included == 5

    def test_budget_utilization(self) -> None:
        """Test budget_utilization property."""
        ctx = AssembledContext(
            content="test",
            total_tokens=800,
            context_count=5,
            budget_total=1000,
            budget_used=800,
        )

        assert ctx.budget_utilization == 0.8

    def test_budget_utilization_zero_budget(self) -> None:
        """Test budget_utilization with zero budget."""
        ctx = AssembledContext(
            content="test",
            total_tokens=0,
            context_count=0,
            budget_total=0,
            budget_used=0,
        )

        assert ctx.budget_utilization == 0.0

    def test_to_dict(self) -> None:
        """Test to_dict method."""
        ctx = AssembledContext(
            content="test",
            total_tokens=100,
            context_count=2,
            assembly_time_ms=50.123,
        )

        data = ctx.to_dict()
        assert data["content"] == "test"
        assert data["total_tokens"] == 100
        assert data["context_count"] == 2
        assert data["assembly_time_ms"] == 50.12  # Rounded

    def test_to_json_and_from_json(self) -> None:
        """Test JSON serialization round-trip."""
        original = AssembledContext(
            content="Test content",
            total_tokens=100,
            context_count=3,
            excluded_count=2,
            assembly_time_ms=45.5,
            model="claude-3-sonnet",
            budget_total=1000,
            budget_used=100,
            by_type={"system": 20, "knowledge": 80},
            cache_hit=True,
            cache_key="abc123",
        )

        json_str = original.to_json()
        restored = AssembledContext.from_json(json_str)

        assert restored.content == original.content
        assert restored.total_tokens == original.total_tokens
        assert restored.context_count == original.context_count
        assert restored.excluded_count == original.excluded_count
        assert restored.model == original.model
        assert restored.cache_hit == original.cache_hit
        assert restored.cache_key == original.cache_key


class TestBaseContextMethods:
    """Tests for BaseContext methods."""

    def test_get_age_seconds(self) -> None:
        """Test get_age_seconds method."""
        old_time = datetime.now(UTC) - timedelta(hours=2)
        ctx = SystemContext(content="test", source="test", timestamp=old_time)

        age = ctx.get_age_seconds()
        # Should be approximately 2 hours in seconds
        assert 7100 < age < 7300

    def test_get_age_hours(self) -> None:
        """Test get_age_hours method."""
        old_time = datetime.now(UTC) - timedelta(hours=5)
        ctx = SystemContext(content="test", source="test", timestamp=old_time)

        age = ctx.get_age_hours()
        assert 4.9 < age < 5.1

    def test_is_stale(self) -> None:
        """Test is_stale method."""
        old_time = datetime.now(UTC) - timedelta(days=10)
        new_time = datetime.now(UTC) - timedelta(hours=1)

        old_ctx = SystemContext(content="test", source="test", timestamp=old_time)
        new_ctx = SystemContext(content="test", source="test", timestamp=new_time)

        # Default max_age is 168 hours (7 days)
        assert old_ctx.is_stale() is True
        assert new_ctx.is_stale() is False

    def test_token_count_property(self) -> None:
        """Test token_count property."""
        ctx = SystemContext(content="test", source="test")

        # Initially None
        assert ctx.token_count is None

        # Can be set
        ctx.token_count = 100
        assert ctx.token_count == 100

    def test_score_property_clamping(self) -> None:
        """Test that score is clamped to 0.0-1.0."""
        ctx = SystemContext(content="test", source="test")

        ctx.score = 1.5
        assert ctx.score == 1.0

        ctx.score = -0.5
        assert ctx.score == 0.0

        ctx.score = 0.75
        assert ctx.score == 0.75

    def test_hash_and_equality(self) -> None:
        """Test hash and equality based on ID."""
        ctx1 = SystemContext(content="test", source="test")
        ctx2 = SystemContext(content="test", source="test")
        ctx3 = SystemContext(content="test", source="test")
        ctx3.id = ctx1.id  # Same ID as ctx1

        # Different IDs = not equal
        assert ctx1 != ctx2

        # Same ID = equal
        assert ctx1 == ctx3

        # Can be used in sets
        context_set = {ctx1, ctx2, ctx3}
        assert len(context_set) == 2  # ctx1 and ctx3 are same

    def test_truncate(self) -> None:
        """Test truncate method."""
        long_content = "word " * 1000  # Long content
        ctx = SystemContext(content=long_content, source="test")
        ctx.token_count = 1000

        truncated = ctx.truncate(100)

        assert len(truncated) < len(long_content)
        assert "[truncated]" in truncated
