"""Tests for mission LLM schemas and prompts.

Tests verify:
- MissionFileRefResponse, MissionItemResponse, ChainResponse, CategoryMissionsResponse
  construct correctly with all required fields
- MISSION_PROMPT renders with correct variables and message types
"""

from __future__ import annotations

import pytest

from unfamiliar.llm.missions.prompts import (
    MISSION_HUMAN_PROMPT,
    MISSION_PROMPT,
    MISSION_SYSTEM_PROMPT,
)
from unfamiliar.llm.missions.schemas import (
    CategoryMissionsResponse,
    ChainResponse,
    MissionFileRefResponse,
    MissionItemResponse,
)


class TestMissionFileRefResponseSchema:
    """Tests for MissionFileRefResponse Pydantic model."""

    def test_valid_mission_file_ref_all_fields(self):
        """MissionFileRefResponse with all 4 fields constructs correctly."""
        file_ref = MissionFileRefResponse(
            file_path="src/auth/login.py",
            line_start=42,
            line_end=67,
            description="Contains the login handler that validates credentials.",
        )

        assert file_ref.file_path == "src/auth/login.py"
        assert file_ref.line_start == 42
        assert file_ref.line_end == 67
        assert (
            file_ref.description
            == "Contains the login handler that validates credentials."
        )

    def test_mission_file_ref_with_null_line_values_is_valid(self):
        """MissionFileRefResponse with line_start=None, line_end=None is valid
        (whole-file reference).
        """
        file_ref = MissionFileRefResponse(
            file_path="src/utils/helpers.py",
            line_start=None,
            line_end=None,
            description="Utility functions used across the codebase.",
        )

        assert file_ref.file_path == "src/utils/helpers.py"
        assert file_ref.line_start is None
        assert file_ref.line_end is None
        assert file_ref.description == "Utility functions used across the codebase."


class TestMissionItemResponseSchema:
    """Tests for MissionItemResponse Pydantic model."""

    def test_valid_mission_item_all_fields(self):
        """MissionItemResponse with all 12 fields constructs correctly."""
        file_refs = [
            MissionFileRefResponse(
                file_path="src/auth/login.py",
                line_start=10,
                line_end=50,
                description="Login handler.",
            )
        ]

        mission = MissionItemResponse(
            title="The Password Vault",
            title_plain="Understand Password Storage",
            description="Investigate how user passwords are hashed and stored.",
            difficulty="medium",
            completion_condition="You can explain how passwords are securely stored.",
            security_tag=True,
            standard_tag="security",
            narrative_intro="The old safe in the corner caught your eye...",
            narrative_outro="Another case closed. The passwords were secure after all.",
            file_refs=file_refs,
            module_index=0,
            prerequisite_titles=["Understand Authentication Flow"],
        )

        assert mission.title == "The Password Vault"
        assert mission.title_plain == "Understand Password Storage"
        assert (
            mission.description
            == "Investigate how user passwords are hashed and stored."
        )
        assert mission.difficulty == "medium"
        assert (
            mission.completion_condition
            == "You can explain how passwords are securely stored."
        )
        assert mission.security_tag is True
        assert mission.standard_tag == "security"
        assert (
            mission.narrative_intro == "The old safe in the corner caught your eye..."
        )
        assert (
            mission.narrative_outro
            == "Another case closed. The passwords were secure after all."
        )
        assert len(mission.file_refs) == 1
        assert mission.module_index == 0
        assert mission.prerequisite_titles == ["Understand Authentication Flow"]

    @pytest.mark.parametrize("difficulty", ["quick", "medium", "deep_dive"])
    def test_difficulty_values_accepted(self, difficulty: str):
        """MissionItemResponse accepts quick, medium, and deep_dive
        difficulty values.
        """
        mission = MissionItemResponse(
            title="Test Mission",
            title_plain="Test Mission",
            description="A test mission.",
            difficulty=difficulty,
            completion_condition="Complete the test.",
            security_tag=False,
            narrative_intro="The test begins...",
            narrative_outro="The test ends.",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],
        )

        assert mission.difficulty == difficulty

    def test_standard_tag_none_is_valid(self):
        """MissionItemResponse with standard_tag=None is valid (optional field)."""
        mission = MissionItemResponse(
            title="Test Mission",
            title_plain="Test Mission",
            description="A test mission.",
            difficulty="quick",
            completion_condition="Complete the test.",
            security_tag=False,
            standard_tag=None,
            narrative_intro="The test begins...",
            narrative_outro="The test ends.",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],
        )

        assert mission.standard_tag is None

    def test_empty_prerequisite_titles_is_valid(self):
        """MissionItemResponse with empty prerequisite_titles is valid
        (root mission).
        """
        mission = MissionItemResponse(
            title="Entry Point Mission",
            title_plain="Entry Point Mission",
            description="First mission in the category.",
            difficulty="quick",
            completion_condition="Complete the entry mission.",
            security_tag=False,
            narrative_intro="The investigation begins...",
            narrative_outro="First lead found.",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],  # Empty = root mission
        )

        assert mission.prerequisite_titles == []

    def test_empty_file_refs_is_valid(self):
        """MissionItemResponse with empty file_refs is valid."""
        mission = MissionItemResponse(
            title="Exploratory Mission",
            title_plain="Exploratory Mission",
            description="Explore the codebase broadly.",
            difficulty="deep_dive",
            completion_condition="You have a broad understanding.",
            security_tag=False,
            narrative_intro="The city sprawls before you...",
            narrative_outro="You know the streets now.",
            file_refs=[],  # Empty file refs
            module_index=0,
            prerequisite_titles=[],
        )

        assert mission.file_refs == []


class TestChainResponseSchema:
    """Tests for ChainResponse Pydantic model."""

    def test_valid_chain_response_all_fields(self):
        """ChainResponse with all 4 fields constructs correctly."""
        chain = ChainResponse(
            name="The Authentication Trilogy",
            name_plain="Authentication Flow",
            description=(
                "Follow the complete authentication flow from login to session."
            ),
            mission_titles=["Login Handler", "Session Management", "Token Validation"],
        )

        assert chain.name == "The Authentication Trilogy"
        assert chain.name_plain == "Authentication Flow"
        assert (
            chain.description
            == "Follow the complete authentication flow from login to session."
        )
        assert chain.mission_titles == [
            "Login Handler",
            "Session Management",
            "Token Validation",
        ]

    def test_chain_with_two_mission_titles_is_valid(self):
        """ChainResponse with 2 mission_titles is valid (minimum chain length)."""
        chain = ChainResponse(
            name="Data Flow Duo",
            name_plain="Data Flow Investigation",
            description="Trace data from input to storage.",
            mission_titles=["Input Validation", "Database Storage"],
        )

        assert len(chain.mission_titles) == 2
        assert chain.mission_titles == ["Input Validation", "Database Storage"]


class TestCategoryMissionsResponseSchema:
    """Tests for CategoryMissionsResponse Pydantic model."""

    def test_valid_category_missions_response(self):
        """CategoryMissionsResponse with missions and chains lists
        constructs correctly.
        """
        mission = MissionItemResponse(
            title="Test Mission",
            title_plain="Test Mission",
            description="A test mission.",
            difficulty="quick",
            completion_condition="Done.",
            security_tag=False,
            narrative_intro="Start.",
            narrative_outro="End.",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],
        )
        chain = ChainResponse(
            name="Test Chain",
            name_plain="Test Chain",
            description="A test chain.",
            mission_titles=["Test Mission"],
        )

        response = CategoryMissionsResponse(
            missions=[mission],
            chains=[chain],
        )

        assert len(response.missions) == 1
        assert response.missions[0].title_plain == "Test Mission"
        assert len(response.chains) == 1
        assert response.chains[0].name_plain == "Test Chain"

    def test_category_missions_response_with_empty_chains_is_valid(self):
        """CategoryMissionsResponse with empty chains list is valid."""
        mission = MissionItemResponse(
            title="Solo Mission",
            title_plain="Solo Mission",
            description="A standalone mission.",
            difficulty="quick",
            completion_condition="Done.",
            security_tag=False,
            narrative_intro="Start.",
            narrative_outro="End.",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],
        )

        response = CategoryMissionsResponse(
            missions=[mission],
            chains=[],  # No chains
        )

        assert len(response.missions) == 1
        assert response.chains == []


class TestMissionPromptTemplate:
    """Tests for MISSION_PROMPT ChatPromptTemplate."""

    def test_mission_prompt_renders_with_all_variables(self):
        """MISSION_PROMPT renders without error with all expected variables."""
        from unfamiliar.llm.missions.prompts import build_theme_prompt_vars

        theme_vars = build_theme_prompt_vars(None)  # Use fallback defaults
        messages = MISSION_PROMPT.format_messages(
            category="authentication",
            briefing_summary="This is a test codebase with auth features.",
            category_context="Authentication category specific context.",
            previous_missions="[]",
            module_descriptions="Module 0: Auth handlers\nModule 1: Session management",
            **theme_vars,
        )

        assert len(messages) == 2  # system + human

    def test_system_message_contains_theming(self):
        """Rendered system message contains theming instructions."""
        from unfamiliar.llm.missions.prompts import build_theme_prompt_vars

        theme_vars = build_theme_prompt_vars(None)  # Use fallback defaults
        messages = MISSION_PROMPT.format_messages(
            category="authentication",
            briefing_summary="Test briefing.",
            category_context="Test context.",
            previous_missions="[]",
            module_descriptions="Module 0: Test",
            **theme_vars,
        )

        system_message = messages[0]
        content = system_message.content.lower()

        # Check that theme variables are in the output
        assert "investigation" in content  # fallback theme name
        assert "narrative" in content

    def test_system_message_contains_difficulty_guidelines(self):
        """Rendered system message contains difficulty guidelines
        (quick/medium/deep_dive).
        """
        from unfamiliar.llm.missions.prompts import build_theme_prompt_vars

        theme_vars = build_theme_prompt_vars(None)  # Use fallback defaults
        messages = MISSION_PROMPT.format_messages(
            category="authentication",
            briefing_summary="Test briefing.",
            category_context="Test context.",
            previous_missions="[]",
            module_descriptions="Module 0: Test",
            **theme_vars,
        )

        system_message = messages[0]
        content = system_message.content.lower()

        assert "quick" in content
        assert "medium" in content
        assert "deep_dive" in content

    def test_human_message_contains_injected_category_value(self):
        """Rendered human message includes the injected category value."""
        from unfamiliar.llm.missions.prompts import build_theme_prompt_vars

        theme_vars = build_theme_prompt_vars(None)  # Use fallback defaults
        test_category = "data_persistence"
        messages = MISSION_PROMPT.format_messages(
            category=test_category,
            briefing_summary="Test briefing.",
            category_context="Test context.",
            previous_missions="[]",
            module_descriptions="Module 0: Test",
            **theme_vars,
        )

        human_message = messages[1]
        assert test_category in human_message.content

    def test_human_message_contains_module_descriptions(self):
        """Rendered human message includes module descriptions."""
        from unfamiliar.llm.missions.prompts import build_theme_prompt_vars

        theme_vars = build_theme_prompt_vars(None)  # Use fallback defaults
        test_modules = "Module 0: Authentication\nModule 1: Database"
        messages = MISSION_PROMPT.format_messages(
            category="authentication",
            briefing_summary="Test briefing.",
            category_context="Test context.",
            previous_missions="[]",
            module_descriptions=test_modules,
            **theme_vars,
        )

        human_message = messages[1]
        assert test_modules in human_message.content


class TestMissionPromptConstants:
    """Tests for individual prompt constants."""

    def test_system_prompt_contains_mission_design_principles(self):
        """MISSION_SYSTEM_PROMPT mentions mission design principles."""
        assert "mission" in MISSION_SYSTEM_PROMPT.lower()
        assert "prerequisite" in MISSION_SYSTEM_PROMPT.lower()

    def test_human_prompt_uses_all_required_variables(self):
        """MISSION_HUMAN_PROMPT references all required template variables."""
        assert "{category}" in MISSION_HUMAN_PROMPT
        assert "{briefing_summary}" in MISSION_HUMAN_PROMPT
        assert "{category_context}" in MISSION_HUMAN_PROMPT
        assert "{module_descriptions}" in MISSION_HUMAN_PROMPT
        assert "{previous_missions}" in MISSION_HUMAN_PROMPT
        assert "{theme_name}" in MISSION_HUMAN_PROMPT

    def test_system_prompt_uses_theme_variables(self):
        """MISSION_SYSTEM_PROMPT uses theme template variables."""
        assert "{theme_name}" in MISSION_SYSTEM_PROMPT
        assert "{theme_title_examples}" in MISSION_SYSTEM_PROMPT
        assert "{theme_intro_style}" in MISSION_SYSTEM_PROMPT
        assert "{theme_outro_style}" in MISSION_SYSTEM_PROMPT


class TestMissionItemResponseNegativeValidation:
    """Negative tests for MissionItemResponse validation (GAP-018)."""

    def test_missing_title_raises_validation_error(self):
        """MissionItemResponse without title raises ValidationError."""
        from pydantic import ValidationError

        with pytest.raises(ValidationError) as exc_info:
            MissionItemResponse(
                title_plain="Test",
                description="Test",
                difficulty="quick",
                completion_condition="Done",
                security_tag=False,
                narrative_intro="Start",
                narrative_outro="End",
                file_refs=[],
                module_index=0,
                prerequisite_titles=[],
            )
        assert "title" in str(exc_info.value).lower()

    def test_missing_title_plain_raises_validation_error(self):
        """MissionItemResponse without title_plain raises ValidationError."""
        from pydantic import ValidationError

        with pytest.raises(ValidationError) as exc_info:
            MissionItemResponse(
                title="Test",
                description="Test",
                difficulty="quick",
                completion_condition="Done",
                security_tag=False,
                narrative_intro="Start",
                narrative_outro="End",
                file_refs=[],
                module_index=0,
                prerequisite_titles=[],
            )
        assert "title_plain" in str(exc_info.value).lower()

    def test_missing_description_raises_validation_error(self):
        """MissionItemResponse without description raises ValidationError."""
        from pydantic import ValidationError

        with pytest.raises(ValidationError) as exc_info:
            MissionItemResponse(
                title="Test",
                title_plain="Test",
                difficulty="quick",
                completion_condition="Done",
                security_tag=False,
                narrative_intro="Start",
                narrative_outro="End",
                file_refs=[],
                module_index=0,
                prerequisite_titles=[],
            )
        assert "description" in str(exc_info.value).lower()

    def test_empty_title_is_accepted(self):
        """MissionItemResponse with empty title is accepted by Pydantic
        (no min_length validator).
        """
        mission = MissionItemResponse(
            title="",
            title_plain="Test",
            description="Test",
            difficulty="quick",
            completion_condition="Done",
            security_tag=False,
            narrative_intro="Start",
            narrative_outro="End",
            file_refs=[],
            module_index=0,
            prerequisite_titles=[],
        )
        # Pydantic accepts empty strings by default
        assert mission.title == ""


class TestChainResponseNegativeValidation:
    """Negative tests for ChainResponse validation (GAP-018)."""

    def test_missing_mission_titles_raises_validation_error(self):
        """ChainResponse without mission_titles raises ValidationError."""
        from pydantic import ValidationError

        with pytest.raises(ValidationError) as exc_info:
            ChainResponse(
                name="Test Chain",
                name_plain="Test Chain",
                description="A test chain.",
            )
        assert "mission_titles" in str(exc_info.value).lower()


class TestCategoryMissionsResponseNegativeValidation:
    """Negative tests for CategoryMissionsResponse validation (GAP-018)."""

    def test_missing_missions_raises_validation_error(self):
        """CategoryMissionsResponse without missions raises ValidationError."""
        from pydantic import ValidationError

        with pytest.raises(ValidationError) as exc_info:
            CategoryMissionsResponse(chains=[])
        assert "missions" in str(exc_info.value).lower()

    def test_empty_missions_list_accepted(self):
        """CategoryMissionsResponse with empty missions list is accepted."""
        # Pydantic Field description says "3-8 missions" but no min_length
        # validator - this test documents current behavior
        response = CategoryMissionsResponse(missions=[], chains=[])
        assert response.missions == []
