"""Tests for campaign persister.

Tests verify:
- persist_pipeline_data correctly wires validated data
- Film Noir title fallback in chain linking
- Prerequisite resolution uses validated data
- persist_briefing_data creates briefing and modules
- persist_pipeline_data sets mission status based on prerequisites
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import MagicMock

from tests.conftest import (
    make_briefing_result,
    make_mission_item,
)
from unfamiliar.analysis.models import AnalysisResult, DetectedModule
from unfamiliar.campaign.persister import (
    _persist_category_chains,
    persist_briefing_data,
    persist_pipeline_data,
)
from unfamiliar.constants import InvestigationCategory, MissionStatus
from unfamiliar.llm.briefing.schemas import ModuleDescription
from unfamiliar.llm.missions.pipeline import PipelineResult
from unfamiliar.llm.missions.schemas import (
    CategoryMissionsResponse,
    ChainResponse,
    MissionItemResponse,
)
from unfamiliar.models.analysis import Module

if TYPE_CHECKING:
    from unfamiliar.models.mission import ChainMissionLink, Mission


def make_mission_response(
    title: str,
    title_plain: str,
    prerequisite_titles: list[str] | None = None,
) -> MissionItemResponse:
    """Create a mission response with both title and title_plain."""
    return make_mission_item(
        title=title,
        title_plain=title_plain,
        prerequisite_titles=prerequisite_titles or [],
    )


class TestPersistCategoryChainsFilmNoirFallback:
    """Tests for Film Noir title fallback in _persist_category_chains."""

    def test_film_noir_title_fallback_creates_link(self):
        """Chain using Film Noir title creates link via fallback lookup."""
        db = MagicMock()
        campaign_id = 1
        category = InvestigationCategory.ARCHITECTURE
        warnings: list[str] = []

        # Mission with different Film Noir and title_plain
        mission = make_mission_response(
            title="The Factory Floor",
            title_plain="Understand Factory Pattern",
        )

        # Mission ID map keyed on title_plain
        mission_id_map = {"Understand Factory Pattern": 42}

        # Chain uses Film Noir title
        chain = ChainResponse(
            name="Learning Path",
            name_plain="Learning Path",
            description="A learning path",
            mission_titles=["The Factory Floor"],  # Film Noir title
        )

        # Mock chain repo
        mock_chain_repo = MagicMock()
        mock_chain = MagicMock()
        mock_chain.id = 100
        mock_chain_repo.create.return_value = mock_chain
        db.chain_repo = mock_chain_repo

        # Track add_missions calls
        captured_links: list[ChainMissionLink] = []

        def capture_links(chain_id, links):
            captured_links.extend(links)

        mock_chain_repo.add_missions.side_effect = capture_links

        result = _persist_category_chains(
            db=db,
            campaign_id=campaign_id,
            category=category,
            chains=[chain],
            mission_id_map=mission_id_map,
            warnings=warnings,
            missions=[mission],
        )

        # Should have created the chain
        assert result == 1
        # Should have linked to mission ID 42 via fallback
        assert len(captured_links) == 1
        assert captured_links[0].mission_id == 42
        # Should have a warning about the fallback
        assert any("fallback" in w.lower() for w in warnings)

    def test_title_plain_match_no_fallback_needed(self):
        """Chain using title_plain creates link directly, no fallback warning."""
        db = MagicMock()
        campaign_id = 1
        category = InvestigationCategory.ARCHITECTURE
        warnings: list[str] = []

        mission = make_mission_response(
            title="Film Noir Title",
            title_plain="Plain Title",
        )

        mission_id_map = {"Plain Title": 42}

        # Chain uses title_plain directly
        chain = ChainResponse(
            name="Direct Chain",
            name_plain="Direct Chain",
            description="Direct lookup",
            mission_titles=["Plain Title"],  # Already title_plain
        )

        mock_chain_repo = MagicMock()
        mock_chain = MagicMock()
        mock_chain.id = 100
        mock_chain_repo.create.return_value = mock_chain
        db.chain_repo = mock_chain_repo

        captured_links: list[ChainMissionLink] = []

        def capture_links(chain_id, links):
            captured_links.extend(links)

        mock_chain_repo.add_missions.side_effect = capture_links

        result = _persist_category_chains(
            db=db,
            campaign_id=campaign_id,
            category=category,
            chains=[chain],
            mission_id_map=mission_id_map,
            warnings=warnings,
            missions=[mission],
        )

        assert result == 1
        assert len(captured_links) == 1
        assert captured_links[0].mission_id == 42
        # No fallback warning when title_plain match works
        assert not any("fallback" in w.lower() for w in warnings)

    def test_unknown_title_creates_warning_skips_link(self):
        """Unknown title (not in title_plain or Film Noir) creates
        warning, skips link.
        """
        db = MagicMock()
        campaign_id = 1
        category = InvestigationCategory.ARCHITECTURE
        warnings: list[str] = []

        mission = make_mission_response(
            title="Known Film Noir",
            title_plain="Known Plain",
        )

        mission_id_map = {"Known Plain": 42}

        # Chain uses completely unknown title
        chain = ChainResponse(
            name="Bad Chain",
            name_plain="Bad Chain",
            description="Unknown mission",
            mission_titles=["Totally Unknown Title"],
        )

        mock_chain_repo = MagicMock()
        mock_chain = MagicMock()
        mock_chain.id = 100
        mock_chain_repo.create.return_value = mock_chain
        db.chain_repo = mock_chain_repo

        captured_links: list[ChainMissionLink] = []

        def capture_links(chain_id, links):
            captured_links.extend(links)

        mock_chain_repo.add_missions.side_effect = capture_links

        result = _persist_category_chains(
            db=db,
            campaign_id=campaign_id,
            category=category,
            chains=[chain],
            mission_id_map=mission_id_map,
            warnings=warnings,
            missions=[mission],
        )

        assert result == 1  # Chain still created
        assert len(captured_links) == 0  # No links added
        assert any("unknown" in w.lower() for w in warnings)

    def test_no_missions_provided_no_fallback(self):
        """When missions=None, fallback is not used (backwards compatible)."""
        db = MagicMock()
        campaign_id = 1
        category = InvestigationCategory.ARCHITECTURE
        warnings: list[str] = []

        mission_id_map = {"Known Plain": 42}

        # Chain uses Film Noir title
        chain = ChainResponse(
            name="No Fallback Chain",
            name_plain="No Fallback Chain",
            description="No missions provided",
            mission_titles=["Some Film Noir Title"],
        )

        mock_chain_repo = MagicMock()
        mock_chain = MagicMock()
        mock_chain.id = 100
        mock_chain_repo.create.return_value = mock_chain
        db.chain_repo = mock_chain_repo

        captured_links: list[ChainMissionLink] = []

        def capture_links(chain_id, links):
            captured_links.extend(links)

        mock_chain_repo.add_missions.side_effect = capture_links

        result = _persist_category_chains(
            db=db,
            campaign_id=campaign_id,
            category=category,
            chains=[chain],
            mission_id_map=mission_id_map,
            warnings=warnings,
            missions=None,  # No missions provided
        )

        assert result == 1
        # Without missions, fallback can't work - link should be skipped
        assert len(captured_links) == 0
        assert any("unknown" in w.lower() for w in warnings)


class TestResolveAllPrerequisites:
    """Tests for _resolve_all_prerequisites using validated data."""

    def test_uses_validated_prerequisites_skips_removed_bad_prereq(self):
        """Prerequisite resolution uses validated data - removed bad
        prereqs are skipped.
        """
        from unfamiliar.campaign.persister import _resolve_all_prerequisites
        from unfamiliar.llm.missions.schemas import CategoryMissionsResponse

        db = MagicMock()
        warnings: list[str] = []

        # Create two missions - one root, one dependent
        # The dependent mission had a bad prerequisite that was REMOVED by validation
        root_mission = make_mission_response(
            title="The Beginning",
            title_plain="Start Here",
        )
        # Simulating post-validation state: prereq "Nonexistent Mission" was removed
        dependent_mission = make_mission_response(
            title="The Next Step",
            title_plain="Continue Learning",
        )
        dependent_mission.prerequisite_titles = ["Start Here"]  # Valid, kept prereq

        # Build pipeline result with validated missions
        category_response = CategoryMissionsResponse(
            missions=[root_mission, dependent_mission],
            chains=[],
        )

        pipeline_result = PipelineResult()
        pipeline_result.category_missions["architecture"] = category_response
        pipeline_result.completed_categories = ["architecture"]

        # Mission ID map (simulating what _persist_category_missions would return)
        category_mission_lookup = {
            "architecture": {
                "Start Here": 1,
                "Continue Learning": 2,
            }
        }

        # Track add_prerequisites calls
        captured_prereqs: list[tuple[int, list[int]]] = []

        def capture_prereqs(mission_id, prereq_ids):
            captured_prereqs.append((mission_id, prereq_ids))

        db.mission_file_ref_repo.add_prerequisites.side_effect = capture_prereqs

        _resolve_all_prerequisites(
            db=db,
            pipeline_result=pipeline_result,
            category_mission_lookup=category_mission_lookup,
            warnings=warnings,
        )

        # Should have created prereq link for mission 2 -> mission 1
        assert len(captured_prereqs) == 1
        assert captured_prereqs[0][0] == 2  # dependent mission ID
        assert captured_prereqs[0][1] == [1]  # prereq mission ID
        # No warning about missing prereq because validation already cleaned it
        assert not any("unknown" in w.lower() for w in warnings)

    def test_reports_unknown_prereq_if_not_in_lookup(self):
        """Prerequisite not in mission_id_map is reported as unknown."""
        from unfamiliar.campaign.persister import _resolve_all_prerequisites
        from unfamiliar.llm.missions.schemas import CategoryMissionsResponse

        db = MagicMock()
        warnings: list[str] = []

        # Mission with a prereq that doesn't exist in the lookup
        mission = make_mission_response(
            title="Orphan Mission",
            title_plain="Orphan Mission",
        )
        mission.prerequisite_titles = ["Missing Parent"]

        category_response = CategoryMissionsResponse(
            missions=[mission],
            chains=[],
        )

        pipeline_result = PipelineResult()
        pipeline_result.category_missions["architecture"] = category_response
        pipeline_result.completed_categories = ["architecture"]

        # Mission ID map without the prereq
        category_mission_lookup = {
            "architecture": {
                "Orphan Mission": 1,
                # "Missing Parent" intentionally not in lookup
            }
        }

        _resolve_all_prerequisites(
            db=db,
            pipeline_result=pipeline_result,
            category_mission_lookup=category_mission_lookup,
            warnings=warnings,
        )
        # Should have a warning about unknown prerequisite
        assert any(
            "unknown" in w.lower() and "missing parent" in w.lower() for w in warnings
        )
        # Should NOT have called add_prerequisites
        db.mission_file_ref_repo.add_prerequisites.assert_not_called()


class TestPersistBriefingData:
    """Tests for persist_briefing_data function."""

    def test_creates_briefing_and_modules_from_result(self):
        """Valid BriefingResult with 3 modules creates briefing and 3
        modules with IDs.
        """

        from unfamiliar.llm.briefing.schemas import BriefingResponse

        db = MagicMock()
        campaign_id = 1

        # Create BriefingResult with 3 modules
        module_descriptions = [
            ModuleDescription(
                name="Authentication",
                description="Handles user authentication",
                security_relevant=True,
            ),
            ModuleDescription(
                name="Database",
                description="Database models and queries",
                security_relevant=False,
            ),
            ModuleDescription(
                name="API Routes",
                description="REST API endpoints",
                security_relevant=False,
            ),
        ]

        briefing_response = BriefingResponse(
            summary="Test codebase summary.",
            tech_stack=["Python", "FastAPI"],
            security_posture="No significant concerns.",
            modules=module_descriptions,
        )

        briefing_result = make_briefing_result(
            briefing=briefing_response,
            modules=module_descriptions,
        )

        # Mock briefing creation
        db.campaign_repo.create_briefing.return_value = None

        # Mock module creation - return modules with IDs
        persisted_modules = [
            Module(
                id=101,
                campaign_id=campaign_id,
                name="Authentication",
                path="",
                description="Handles user authentication",
                file_count=0,
                security_relevant=True,
            ),
            Module(
                id=102,
                campaign_id=campaign_id,
                name="Database",
                path="",
                description="Database models and queries",
                file_count=0,
                security_relevant=False,
            ),
            Module(
                id=103,
                campaign_id=campaign_id,
                name="API Routes",
                path="",
                description="REST API endpoints",
                file_count=0,
                security_relevant=False,
            ),
        ]
        db.module_repo.create_many.return_value = persisted_modules

        result = persist_briefing_data(
            db=db,
            campaign_id=campaign_id,
            briefing_result=briefing_result,
            analysis=None,
        )

        # Briefing should be created
        db.campaign_repo.create_briefing.assert_called_once()
        briefing_arg = db.campaign_repo.create_briefing.call_args[0][0]
        assert briefing_arg.campaign_id == campaign_id
        assert briefing_arg.summary == "Test codebase summary."
        assert briefing_arg.tech_stack == ["Python", "FastAPI"]
        assert briefing_arg.security_posture == "No significant concerns."

        # Module repo should be called with modules
        db.module_repo.create_many.assert_called_once()
        modules_arg = db.module_repo.create_many.call_args[0][0]
        assert len(modules_arg) == 3

        # Result should be the persisted modules with IDs
        assert len(result) == 3
        assert result[0].id == 101
        assert result[1].id == 102
        assert result[2].id == 103

    def test_zero_modules_creates_briefing_only(self):
        """BriefingResult with empty modules list creates briefing,
        returns empty list.
        """
        from unfamiliar.llm.briefing.schemas import BriefingResponse

        db = MagicMock()
        campaign_id = 1

        briefing_response = BriefingResponse(
            summary="Empty codebase.",
            tech_stack=[],
            security_posture="Nothing to assess.",
            modules=[],
        )

        briefing_result = make_briefing_result(
            briefing=briefing_response,
            modules=[],
        )

        db.campaign_repo.create_briefing.return_value = None
        db.module_repo.create_many.return_value = []

        result = persist_briefing_data(
            db=db,
            campaign_id=campaign_id,
            briefing_result=briefing_result,
            analysis=None,
        )

        # Briefing should still be created
        db.campaign_repo.create_briefing.assert_called_once()

        # Module repo called with empty list
        db.module_repo.create_many.assert_called_once()
        modules_arg = db.module_repo.create_many.call_args[0][0]
        assert modules_arg == []

        # Result is empty list
        assert result == []

    def test_module_ordering_preserved(self):
        """Modules returned in same order as input for index-based linking."""

        db = MagicMock()
        campaign_id = 1

        # Create modules in specific order
        module_descriptions = [
            ModuleDescription(
                name="First Module",
                description="Should be index 0",
                security_relevant=False,
            ),
            ModuleDescription(
                name="Second Module",
                description="Should be index 1",
                security_relevant=False,
            ),
            ModuleDescription(
                name="Third Module",
                description="Should be index 2",
                security_relevant=False,
            ),
        ]

        briefing_result = make_briefing_result(modules=module_descriptions)

        db.campaign_repo.create_briefing.return_value = None

        # Return persisted modules in the same order they were passed
        persisted_modules = [
            Module(
                id=1,
                campaign_id=campaign_id,
                name="First Module",
                path="",
                description="Should be index 0",
            ),
            Module(
                id=2,
                campaign_id=campaign_id,
                name="Second Module",
                path="",
                description="Should be index 1",
            ),
            Module(
                id=3,
                campaign_id=campaign_id,
                name="Third Module",
                path="",
                description="Should be index 2",
            ),
        ]
        db.module_repo.create_many.return_value = persisted_modules

        result = persist_briefing_data(
            db=db,
            campaign_id=campaign_id,
            briefing_result=briefing_result,
            analysis=None,
        )

        # Order must be preserved: First, Second, Third
        assert result[0].name == "First Module"
        assert result[1].name == "Second Module"
        assert result[2].name == "Third Module"

    def test_analysis_structural_data_merged_into_modules(self):
        """When analysis provided, modules get path and file_count from
        AnalysisResult.
        """

        db = MagicMock()
        campaign_id = 1

        module_descriptions = [
            ModuleDescription(
                name="Auth Module",
                description="Authentication handlers",
                security_relevant=True,
            ),
            ModuleDescription(
                name="Core Module",
                description="Core business logic",
                security_relevant=False,
            ),
        ]

        briefing_result = make_briefing_result(modules=module_descriptions)

        # Analysis with structural data
        detected_modules = [
            DetectedModule(
                files=["auth/login.py", "auth/logout.py"],
                primary_path="src/auth/",
                file_count=2,
                confidence=0.9,
                category_hints=[InvestigationCategory.DOMAIN_BUSINESS_LOGIC],
            ),
            DetectedModule(
                files=["core/logic.py", "core/utils.py", "core/helpers.py"],
                primary_path="src/core/",
                file_count=3,
                confidence=0.85,
                category_hints=[InvestigationCategory.ARCHITECTURE],
            ),
        ]
        analysis = AnalysisResult(
            files=[],
            parsed_files={},
            modules=detected_modules,
            repo_path="/test/repo",
        )

        db.campaign_repo.create_briefing.return_value = None
        db.module_repo.create_many.return_value = [
            Module(
                id=1,
                campaign_id=campaign_id,
                name="Auth Module",
                path="src/auth/",
                description="Authentication handlers",
                file_count=2,
                category_relevance=[InvestigationCategory.DOMAIN_BUSINESS_LOGIC],
                security_relevant=True,
            ),
            Module(
                id=2,
                campaign_id=campaign_id,
                name="Core Module",
                path="src/core/",
                description="Core business logic",
                file_count=3,
                category_relevance=[InvestigationCategory.ARCHITECTURE],
                security_relevant=False,
            ),
        ]

        result = persist_briefing_data(
            db=db,
            campaign_id=campaign_id,
            briefing_result=briefing_result,
            analysis=analysis,
        )

        # Verify create_many was called with structural data
        modules_arg = db.module_repo.create_many.call_args[0][0]
        assert len(modules_arg) == 2

        # First module gets path and file_count from first DetectedModule
        assert modules_arg[0].path == "src/auth/"
        assert modules_arg[0].file_count == 2
        assert (
            InvestigationCategory.DOMAIN_BUSINESS_LOGIC
            in modules_arg[0].category_relevance
        )

        # Second module gets path and file_count from second DetectedModule
        assert modules_arg[1].path == "src/core/"
        assert modules_arg[1].file_count == 3
        assert InvestigationCategory.ARCHITECTURE in modules_arg[1].category_relevance

        # Result contains persisted modules with structural data
        assert len(result) == 2
        assert result[0].path == "src/auth/"
        assert result[0].file_count == 2
        assert result[1].path == "src/core/"
        assert result[1].file_count == 3


class TestPersistPipelineData:
    """Tests for persist_pipeline_data function."""

    def test_empty_pipeline_result_returns_zero_counts(self):
        """No completed categories returns PersistResult with zero counts."""
        db = MagicMock()
        campaign_id = 1
        modules: list[Module] = []
        repo_path = Path("/test/repo")

        # Empty pipeline result
        pipeline_result = PipelineResult(
            category_missions={},
            completed_categories=[],
            errors=[],
        )

        result = persist_pipeline_data(
            db=db,
            campaign_id=campaign_id,
            pipeline_result=pipeline_result,
            modules=modules,
            repo_path=repo_path,
        )

        # Result should have zero counts and empty warnings
        assert result.mission_count == 0
        assert result.chain_count == 0
        assert result.warning_messages == []

    def test_missions_with_no_prerequisites_set_to_available(self):
        """Root missions (empty prerequisite_titles) get AVAILABLE status."""

        db = MagicMock()
        campaign_id = 1

        # Module for linking
        modules = [
            Module(
                id=10,
                campaign_id=campaign_id,
                name="Test Module",
                path="test/",
                description="Test",
            )
        ]

        # Root missions (no prerequisites)
        root_mission_1 = make_mission_item(
            title="The Beginning",
            title_plain="Start Here",
            prerequisite_titles=[],  # No prerequisites = root mission
            module_index=0,
        )
        root_mission_2 = make_mission_item(
            title="First Steps",
            title_plain="Take First Steps",
            prerequisite_titles=[],  # No prerequisites = root mission
            module_index=0,
        )

        category_response = CategoryMissionsResponse(
            missions=[root_mission_1, root_mission_2],
            chains=[],
        )

        pipeline_result = PipelineResult(
            category_missions={"architecture": category_response},
            completed_categories=["architecture"],
        )

        # Track created missions
        created_missions: list[Mission] = []

        def capture_mission(mission: Mission):
            mission.id = len(created_missions) + 1
            created_missions.append(mission)
            return mission

        db.mission_repo.create.side_effect = capture_mission
        db.mission_file_ref_repo.add_file_refs.return_value = None
        db.mission_link_repo.link_modules.return_value = None
        db.chain_repo = MagicMock()

        result = persist_pipeline_data(
            db=db,
            campaign_id=campaign_id,
            pipeline_result=pipeline_result,
            modules=modules,
            repo_path=Path("/test/repo"),
        )

        # Both root missions should have AVAILABLE status
        assert len(created_missions) == 2
        assert created_missions[0].status == MissionStatus.AVAILABLE
        assert created_missions[1].status == MissionStatus.AVAILABLE
        assert result.mission_count == 2

    def test_missions_with_prerequisites_set_to_locked(self):
        """Missions with non-empty prerequisite_titles get LOCKED status."""

        db = MagicMock()
        campaign_id = 1

        modules = [
            Module(
                id=10,
                campaign_id=campaign_id,
                name="Test Module",
                path="test/",
                description="Test",
            )
        ]

        # Root mission (no prerequisites)
        root_mission = make_mission_item(
            title="The Beginning",
            title_plain="Start Here",
            prerequisite_titles=[],
            module_index=0,
        )

        # Dependent mission (has prerequisites)
        dependent_mission = make_mission_item(
            title="The Next Step",
            title_plain="Continue Learning",
            prerequisite_titles=["Start Here"],  # Has prerequisite
            module_index=0,
        )

        category_response = CategoryMissionsResponse(
            missions=[root_mission, dependent_mission],
            chains=[],
        )

        pipeline_result = PipelineResult(
            category_missions={"architecture": category_response},
            completed_categories=["architecture"],
        )

        created_missions: list[Mission] = []

        def capture_mission(mission: Mission):
            mission.id = len(created_missions) + 1
            created_missions.append(mission)
            return mission

        db.mission_repo.create.side_effect = capture_mission
        db.mission_file_ref_repo.add_file_refs.return_value = None
        db.mission_file_ref_repo.add_prerequisites.return_value = None
        db.mission_link_repo.link_modules.return_value = None
        db.chain_repo = MagicMock()

        result = persist_pipeline_data(
            db=db,
            campaign_id=campaign_id,
            pipeline_result=pipeline_result,
            modules=modules,
            repo_path=Path("/test/repo"),
        )

        assert len(created_missions) == 2
        # Root mission should be AVAILABLE
        assert created_missions[0].status == MissionStatus.AVAILABLE
        # Dependent mission should be LOCKED
        assert created_missions[1].status == MissionStatus.LOCKED
        assert result.mission_count == 2

    def test_out_of_range_module_index_defaults_to_zero(self):
        """module_index=99 with only 1 module defaults to index 0, warning logged."""

        db = MagicMock()
        campaign_id = 1

        # Only ONE module
        modules = [
            Module(
                id=10,
                campaign_id=campaign_id,
                name="Only Module",
                path="test/",
                description="Test",
            )
        ]

        # Mission with out-of-range module_index
        bad_index_mission = make_mission_item(
            title="Broken Reference",
            title_plain="Mission With Bad Index",
            module_index=99,  # Out of range!
        )

        category_response = CategoryMissionsResponse(
            missions=[bad_index_mission],
            chains=[],
        )

        pipeline_result = PipelineResult(
            category_missions={"architecture": category_response},
            completed_categories=["architecture"],
        )

        def capture_mission(mission: Mission):
            mission.id = 1
            return mission

        db.mission_repo.create.side_effect = capture_mission
        db.mission_file_ref_repo.add_file_refs.return_value = None

        # Track which module was linked
        linked_module_id: int | None = None

        def capture_link(mission_id, links):
            nonlocal linked_module_id
            if links:
                linked_module_id = links[0].module_id

        db.mission_link_repo.link_modules.side_effect = capture_link
        db.chain_repo = MagicMock()

        result = persist_pipeline_data(
            db=db,
            campaign_id=campaign_id,
            pipeline_result=pipeline_result,
            modules=modules,
            repo_path=Path("/test/repo"),
        )

        # Mission should have been created
        assert result.mission_count == 1

        # Should have a warning about the out-of-range index
        assert len(result.warning_messages) == 1
        assert "out of range" in result.warning_messages[0].lower()
        assert "99" in result.warning_messages[0]

        # Should have linked to module at index 0 (the only module)
        assert linked_module_id == 10


class TestParseDifficulty:
    """Tests for _parse_difficulty helper function."""

    def test_quick_returns_quick(self):
        """'quick' returns MissionDifficulty.QUICK."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        result = _parse_difficulty("quick")
        assert result == MissionDifficulty.QUICK

    def test_medium_returns_medium(self):
        """'medium' returns MissionDifficulty.MEDIUM."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        result = _parse_difficulty("medium")
        assert result == MissionDifficulty.MEDIUM

    def test_deep_dive_returns_deep_dive(self):
        """'deep_dive' returns MissionDifficulty.DEEP_DIVE."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        result = _parse_difficulty("deep_dive")
        assert result == MissionDifficulty.DEEP_DIVE

    def test_unknown_value_defaults_to_medium(self):
        """Unknown difficulty string defaults to MEDIUM with warning."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        result = _parse_difficulty("unknown_value")
        assert result == MissionDifficulty.MEDIUM

    def test_empty_string_defaults_to_medium(self):
        """Empty string defaults to MEDIUM."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        result = _parse_difficulty("")
        assert result == MissionDifficulty.MEDIUM

    def test_case_sensitive(self):
        """Difficulty parsing is case-sensitive."""
        from unfamiliar.campaign.persister import _parse_difficulty
        from unfamiliar.constants import MissionDifficulty

        # 'QUICK' (uppercase) should not match 'quick'
        result = _parse_difficulty("QUICK")
        assert result == MissionDifficulty.MEDIUM  # Falls back to default
