"""Tests for the Prompt Composer module."""

import pytest

from mirdan.config import EnhancementConfig
from mirdan.core.prompt_composer import PromptComposer
from mirdan.core.quality_standards import QualityStandards
from mirdan.models import ContextBundle, EnhancedPrompt, Intent, TaskType, ToolRecommendation


@pytest.fixture
def standards() -> QualityStandards:
    """Create a QualityStandards instance."""
    return QualityStandards()


@pytest.fixture
def composer(standards: QualityStandards) -> PromptComposer:
    """Create a PromptComposer instance."""
    return PromptComposer(standards)


class TestPromptComposerInit:
    """Tests for PromptComposer initialization."""

    def test_init_with_standards_only(self, standards: QualityStandards) -> None:
        """Should store standards and have None config."""
        composer = PromptComposer(standards)
        assert composer.standards is standards
        assert composer._config is None

    def test_init_with_config(self, standards: QualityStandards) -> None:
        """Should store both standards and config."""
        config = EnhancementConfig(verbosity="minimal")
        composer = PromptComposer(standards, config=config)
        assert composer.standards is standards
        assert composer._config is config


class TestCompose:
    """Tests for compose method."""

    def test_compose_returns_enhanced_prompt(self, composer: PromptComposer) -> None:
        """Should return EnhancedPrompt instance."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert isinstance(result, EnhancedPrompt)

    def test_compose_includes_quality_requirements(self, composer: PromptComposer) -> None:
        """Should populate quality_requirements."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            primary_language="python",
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert len(result.quality_requirements) > 0

    def test_compose_includes_verification_steps(self, composer: PromptComposer) -> None:
        """Should populate verification_steps."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert len(result.verification_steps) >= 4  # Base steps

    def test_compose_includes_tool_recommendations(self, composer: PromptComposer) -> None:
        """Should pass through tool_recommendations."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        recommendations = [ToolRecommendation(mcp="test", action="test action", priority="high")]
        result = composer.compose(intent, context, recommendations)
        assert result.tool_recommendations == recommendations


class TestVerificationSteps:
    """Tests for generate_verification_steps method."""

    def test_base_steps_always_included(self, composer: PromptComposer) -> None:
        """Should include 4 base verification steps."""
        intent = Intent(original_prompt="test", task_type=TaskType.UNKNOWN)
        steps = composer.generate_verification_steps(intent)
        assert len(steps) >= 4
        assert any("imports" in s.lower() for s in steps)
        assert any("error handling" in s.lower() for s in steps)
        assert any("secrets" in s.lower() or "credentials" in s.lower() for s in steps)
        assert any("naming conventions" in s.lower() for s in steps)

    def test_generation_task_adds_integration_step(self, composer: PromptComposer) -> None:
        """Should add integration validation step for GENERATION tasks."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        steps = composer.generate_verification_steps(intent)
        assert any("integrates with existing patterns" in s.lower() for s in steps)

    def test_refactor_task_adds_preservation_step(self, composer: PromptComposer) -> None:
        """Should insert functionality preservation step at position 0 for REFACTOR."""
        intent = Intent(original_prompt="test", task_type=TaskType.REFACTOR)
        steps = composer.generate_verification_steps(intent)
        assert "functionality is preserved" in steps[0].lower()

    def test_refactor_task_adds_api_signature_step(self, composer: PromptComposer) -> None:
        """Should append API signature step for REFACTOR."""
        intent = Intent(original_prompt="test", task_type=TaskType.REFACTOR)
        steps = composer.generate_verification_steps(intent)
        assert any("api signatures" in s.lower() for s in steps)

    def test_debug_task_adds_root_cause_step(self, composer: PromptComposer) -> None:
        """Should insert root cause step at position 0 for DEBUG."""
        intent = Intent(original_prompt="test", task_type=TaskType.DEBUG)
        steps = composer.generate_verification_steps(intent)
        assert "root cause" in steps[0].lower()

    def test_debug_task_adds_regression_test_step(self, composer: PromptComposer) -> None:
        """Should append regression test step for DEBUG."""
        intent = Intent(original_prompt="test", task_type=TaskType.DEBUG)
        steps = composer.generate_verification_steps(intent)
        assert any("regression" in s.lower() for s in steps)

    def test_test_task_adds_coverage_steps(self, composer: PromptComposer) -> None:
        """Should add test coverage steps for TEST tasks."""
        intent = Intent(original_prompt="test", task_type=TaskType.TEST)
        steps = composer.generate_verification_steps(intent)
        assert any("edge cases" in s.lower() for s in steps)
        assert any("isolation" in s.lower() or "shared state" in s.lower() for s in steps)

    def test_security_task_adds_security_steps(self, composer: PromptComposer) -> None:
        """Should add security verification steps when touches_security=True."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_security=True,
        )
        steps = composer.generate_verification_steps(intent)
        assert any("password" in s.lower() for s in steps)
        assert any("sensitive data" in s.lower() or "logged" in s.lower() for s in steps)
        assert any("sanitiz" in s.lower() for s in steps)

    def test_kg_task_adds_graph_verification_steps(self, composer: PromptComposer) -> None:
        """Should add KG-specific steps when touches_knowledge_graph=True."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=True,
        )
        steps = composer.generate_verification_steps(intent)
        assert any("graph queries" in s.lower() or "parameterized" in s.lower() for s in steps)
        assert any("traversal" in s.lower() or "depth" in s.lower() for s in steps)
        assert any("deduplication" in s.lower() for s in steps)

    def test_kg_without_rag_still_adds_kg_steps(self, composer: PromptComposer) -> None:
        """KG steps should be added even without touches_rag."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=True,
            touches_rag=False,
        )
        steps = composer.generate_verification_steps(intent)
        assert any("graph queries" in s.lower() for s in steps)

    def test_no_kg_flag_skips_kg_steps(self, composer: PromptComposer) -> None:
        """Should not add KG steps when touches_knowledge_graph is False."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_knowledge_graph=False,
        )
        steps = composer.generate_verification_steps(intent)
        assert not any("graph queries" in s.lower() for s in steps)
        assert not any("deduplication" in s.lower() for s in steps)


class TestBuildPromptText:
    """Tests for _build_prompt_text method."""

    def test_minimal_verbosity_excludes_requirements(self, standards: QualityStandards) -> None:
        """Should skip Quality Requirements section when verbosity is minimal."""
        config = EnhancementConfig(verbosity="minimal")
        composer = PromptComposer(standards, config=config)
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            primary_language="python",
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "## Quality Requirements" not in result.enhanced_text

    def test_balanced_verbosity_limits_requirements(self, standards: QualityStandards) -> None:
        """Should show only first 5 requirements when verbosity is balanced."""
        config = EnhancementConfig(verbosity="balanced")
        composer = PromptComposer(standards, config=config)
        # Create intent that generates many requirements
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            primary_language="python",
            frameworks=["fastapi", "react"],
            touches_security=True,
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        # Count requirement lines (starting with "- ") in Quality Requirements section only
        req_section = result.enhanced_text.split("## Quality Requirements")
        if len(req_section) > 1:
            # Get text up to the next section header
            section_text = req_section[1].split("##")[0]
            req_lines = [line for line in section_text.split("\n") if line.strip().startswith("- ")]
            assert len(req_lines) <= 5

    def test_comprehensive_verbosity_shows_all(self, standards: QualityStandards) -> None:
        """Should show all requirements when verbosity is comprehensive."""
        config = EnhancementConfig(verbosity="comprehensive")
        composer = PromptComposer(standards, config=config)
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            primary_language="python",
            frameworks=["fastapi"],
            touches_security=True,
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "## Quality Requirements" in result.enhanced_text

    def test_include_verification_false_hides_section(self, standards: QualityStandards) -> None:
        """Should hide verification section when include_verification=False."""
        config = EnhancementConfig(include_verification=False)
        composer = PromptComposer(standards, config=config)
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "## Before Completing" not in result.enhanced_text

    def test_include_tool_hints_false_hides_section(self, standards: QualityStandards) -> None:
        """Should hide tool recommendations when include_tool_hints=False."""
        config = EnhancementConfig(include_tool_hints=False)
        composer = PromptComposer(standards, config=config)
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        recommendations = [ToolRecommendation(mcp="test", action="test action")]
        result = composer.compose(intent, context, recommendations)
        assert "## Recommended Tools" not in result.enhanced_text

    def test_context_section_when_patterns_exist(self, composer: PromptComposer) -> None:
        """Should render context section when patterns exist."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle(
            existing_patterns=["def my_pattern(): ..."],
            tech_stack={"python": "3.13"},
        )
        result = composer.compose(intent, context, [])
        assert "## Codebase Context" in result.enhanced_text

    def test_no_context_section_when_empty(self, composer: PromptComposer) -> None:
        """Should skip context section when context is empty."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "## Codebase Context" not in result.enhanced_text

    def test_role_section_uses_language(self, composer: PromptComposer) -> None:
        """Should include language in role section."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            primary_language="python",
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "python developer" in result.enhanced_text.lower()

    def test_role_section_uses_frameworks(self, composer: PromptComposer) -> None:
        """Should include frameworks in role section."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            frameworks=["fastapi", "react"],
        )
        context = ContextBundle()
        result = composer.compose(intent, context, [])
        assert "fastapi" in result.enhanced_text.lower()
        assert "react" in result.enhanced_text.lower()


class TestTaskConstraints:
    """Tests for _get_task_constraints method."""

    def test_base_constraints_always_included(self, composer: PromptComposer) -> None:
        """Should include 2 base constraints for any task type."""
        intent = Intent(original_prompt="test", task_type=TaskType.UNKNOWN)
        constraints = composer._get_task_constraints(intent)
        assert len(constraints) >= 2
        assert any("existing patterns" in c.lower() for c in constraints)
        assert any("dependencies" in c.lower() for c in constraints)

    def test_refactor_constraints(self, composer: PromptComposer) -> None:
        """Should add 3 additional constraints for REFACTOR."""
        intent = Intent(original_prompt="test", task_type=TaskType.REFACTOR)
        constraints = composer._get_task_constraints(intent)
        assert any("preserve" in c.lower() for c in constraints)
        assert any("backward compatibility" in c.lower() for c in constraints)
        assert any("api signatures" in c.lower() for c in constraints)

    def test_debug_constraints(self, composer: PromptComposer) -> None:
        """Should add 2 additional constraints for DEBUG."""
        intent = Intent(original_prompt="test", task_type=TaskType.DEBUG)
        constraints = composer._get_task_constraints(intent)
        assert any("root cause" in c.lower() for c in constraints)
        assert any(
            "minimize changes" in c.lower() or "unrelated code" in c.lower() for c in constraints
        )

    def test_generation_constraints(self, composer: PromptComposer) -> None:
        """Should add 2 additional constraints for GENERATION."""
        intent = Intent(original_prompt="test", task_type=TaskType.GENERATION)
        constraints = composer._get_task_constraints(intent)
        assert any("single responsibility" in c.lower() for c in constraints)
        assert any("easy to test" in c.lower() for c in constraints)

    def test_security_constraints(self, composer: PromptComposer) -> None:
        """Should add 3 security constraints when touches_security=True."""
        intent = Intent(
            original_prompt="test",
            task_type=TaskType.GENERATION,
            touches_security=True,
        )
        constraints = composer._get_task_constraints(intent)
        assert any("credentials" in c.lower() or "api keys" in c.lower() for c in constraints)
        assert any("parameterized queries" in c.lower() for c in constraints)
        assert any("sanitize" in c.lower() for c in constraints)


class TestPlanningComposition:
    """Test PLANNING-specific prompt composition."""

    @pytest.fixture
    def composer(self, standards: QualityStandards) -> PromptComposer:
        return PromptComposer(standards)

    @pytest.fixture
    def planning_intent(self) -> Intent:
        return Intent(
            original_prompt="Create a plan to implement caching",
            task_type=TaskType.PLANNING,
            primary_language="python",
            frameworks=["fastapi"],
        )

    @pytest.fixture
    def context(self) -> ContextBundle:
        return ContextBundle()

    def test_planning_includes_research_phase(
        self, composer: PromptComposer, planning_intent: Intent, context: ContextBundle
    ) -> None:
        """PLANNING prompt includes research phase requirements."""
        result = composer.compose(planning_intent, context, [])
        assert "Research" in result.enhanced_text
        assert "BEFORE" in result.enhanced_text
        assert "Read" in result.enhanced_text

    def test_planning_includes_step_format(
        self, composer: PromptComposer, planning_intent: Intent, context: ContextBundle
    ) -> None:
        """PLANNING prompt includes step format template."""
        result = composer.compose(planning_intent, context, [])
        assert "**File:**" in result.enhanced_text
        assert "**Grounding:**" in result.enhanced_text
        assert "**Verify:**" in result.enhanced_text

    def test_planning_includes_anti_slop(
        self, composer: PromptComposer, planning_intent: Intent, context: ContextBundle
    ) -> None:
        """PLANNING prompt includes anti-slop rules."""
        result = composer.compose(planning_intent, context, [])
        assert "FORBIDDEN" in result.enhanced_text
        assert "should" in result.enhanced_text.lower()
        assert "probably" in result.enhanced_text.lower()

    def test_planning_mentions_less_capable(
        self, composer: PromptComposer, planning_intent: Intent, context: ContextBundle
    ) -> None:
        """PLANNING prompt explains target is less capable model."""
        result = composer.compose(planning_intent, context, [])
        assert (
            "less capable" in result.enhanced_text.lower() or "LESS CAPABLE" in result.enhanced_text
        )

    def test_planning_has_different_verification_steps(
        self, composer: PromptComposer, planning_intent: Intent, context: ContextBundle
    ) -> None:
        """PLANNING verification steps are plan-focused, not code-focused."""
        result = composer.compose(planning_intent, context, [])
        # Planning verification is about plan quality, not code quality
        assert any("Grounding" in step for step in result.verification_steps)
        assert any("line number" in step.lower() for step in result.verification_steps)

    def test_planning_constraints_include_research(
        self, composer: PromptComposer, planning_intent: Intent
    ) -> None:
        """PLANNING constraints include research requirements."""
        constraints = composer._get_task_constraints(planning_intent)
        assert any("research" in c.lower() for c in constraints)
        assert any("atomic" in c.lower() for c in constraints)
