import pytest
from unittest.mock import MagicMock
from context_mixer.commands.assembly_strategies import (
    AssemblyStrategyFactory,
    CopilotAssemblyStrategy,
    ClaudeAssemblyStrategy,
    CursorAssemblyStrategy,
    GenericAssemblyStrategy
)


@pytest.fixture
def mock_chunk():
    """Create a mock knowledge chunk for testing."""
    chunk = MagicMock()
    chunk.id = "test-chunk-1"
    chunk.content = "This is test content for the chunk."
    chunk.metadata.tags = ["test-tag"]
    chunk.metadata.domains = ["testing"]
    chunk.metadata.authority.value = "conventional"
    return chunk


@pytest.fixture
def mock_chunks(mock_chunk):
    """Create a list of mock chunks for testing."""
    return [mock_chunk]


class DescribeAssemblyStrategyFactory:
    def should_create_copilot_strategy_for_copilot_target(self):
        strategy = AssemblyStrategyFactory.create_strategy("copilot")
        assert isinstance(strategy, CopilotAssemblyStrategy)

    def should_create_claude_strategy_for_claude_target(self):
        strategy = AssemblyStrategyFactory.create_strategy("claude")
        assert isinstance(strategy, ClaudeAssemblyStrategy)

    def should_create_cursor_strategy_for_cursor_target(self):
        strategy = AssemblyStrategyFactory.create_strategy("cursor")
        assert isinstance(strategy, CursorAssemblyStrategy)

    def should_create_generic_strategy_for_unknown_target(self):
        strategy = AssemblyStrategyFactory.create_strategy("unknown")
        assert isinstance(strategy, GenericAssemblyStrategy)

    def should_create_generic_strategy_for_generic_target(self):
        strategy = AssemblyStrategyFactory.create_strategy("generic")
        assert isinstance(strategy, GenericAssemblyStrategy)

    def should_handle_case_insensitive_targets(self):
        strategy = AssemblyStrategyFactory.create_strategy("COPILOT")
        assert isinstance(strategy, CopilotAssemblyStrategy)

    def should_return_supported_targets(self):
        targets = AssemblyStrategyFactory.get_supported_targets()
        expected_targets = ['copilot', 'claude', 'cursor', 'generic']
        assert set(targets) == set(expected_targets)


class DescribeCopilotAssemblyStrategy:
    def should_assemble_content_with_copilot_headers(self, mock_chunks):
        strategy = CopilotAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=False)

        assert "# Instructions" in result
        assert "This is test content for the chunk." in result

    def should_assemble_verbose_content_with_full_headers(self, mock_chunks):
        strategy = CopilotAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=True)

        assert "# GitHub Copilot Instructions" in result
        assert "## Project Context" in result
        assert "### Testing Guidelines" in result
        assert "📋 CONVENTIONAL" in result
        assert "Generated by Context Mixer CRAFT system" in result

    def should_respect_token_budget(self, mock_chunks):
        strategy = CopilotAssemblyStrategy()
        # Set a very low token budget
        result = strategy.assemble(mock_chunks, 10, 0.8, verbose=False)

        # Should still contain the header but may not contain all content
        assert "# Instructions" in result


class DescribeClaudeAssemblyStrategy:
    def should_assemble_content_with_claude_headers(self, mock_chunks):
        strategy = ClaudeAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=False)

        assert "# Context" in result
        assert "This is test content for the chunk." in result

    def should_assemble_verbose_content_with_metadata(self, mock_chunks):
        strategy = ClaudeAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=True)

        assert "# Claude Context Instructions" in result
        assert "You are an AI assistant" in result
        assert "## Context 1: test-tag" in result
        assert "**Authority Level:** Conventional" in result
        assert "**Domains:** testing" in result


class DescribeCursorAssemblyStrategy:
    def should_assemble_content_with_cursor_headers(self, mock_chunks):
        strategy = CursorAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=False)

        assert "# Guidelines" in result
        assert "This is test content for the chunk." in result

    def should_assemble_verbose_content_with_full_headers(self, mock_chunks):
        strategy = CursorAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=True)

        assert "# Cursor AI Context" in result
        assert "## Development Guidelines" in result
        assert "### test-tag" in result


class DescribeGenericAssemblyStrategy:
    def should_assemble_content_with_generic_headers(self, mock_chunks):
        strategy = GenericAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=False)

        assert "# Context" in result
        assert "This is test content for the chunk." in result

    def should_assemble_verbose_content_with_all_metadata(self, mock_chunks):
        strategy = GenericAssemblyStrategy()
        result = strategy.assemble(mock_chunks, 1000, 0.8, verbose=True)

        assert "# Assembled Context" in result
        assert "## Section 1" in result
        assert "**ID:** test-chunk-1" in result
        assert "**Authority:** conventional" in result
        assert "**Domains:** testing" in result
        assert "**Tags:** test-tag" in result


class DescribeAssemblyStrategyHelperMethods:
    def should_count_tokens_correctly(self):
        strategy = GenericAssemblyStrategy()
        token_count = strategy._count_tokens("hello world test")
        assert token_count == 3

    def should_get_chunk_tag_from_metadata(self, mock_chunk):
        strategy = GenericAssemblyStrategy()
        tag = strategy._get_chunk_tag(mock_chunk, "default")
        assert tag == "test-tag"

    def should_return_default_tag_when_no_tags(self):
        strategy = GenericAssemblyStrategy()
        chunk = MagicMock()
        chunk.metadata.tags = []
        tag = strategy._get_chunk_tag(chunk, "default")
        assert tag == "default"

    def should_check_token_budget_correctly(self):
        strategy = GenericAssemblyStrategy()
        # Should return True when within budget (90% of 100 = 90)
        assert strategy._check_token_budget(50, 30, 100) == True
        # Should return False when exceeding budget
        assert strategy._check_token_budget(50, 50, 100) == False
