"""
Tests for run_research_process() core execution logic.

Covers:
- Quick vs detailed mode branching
- Settings context creation
- Progress callback invocation
- Termination handling
- Error handling
- Research status updates
"""

import pytest


class TestSettingsContext:
    """Tests for SettingsContext created inside run_research_process."""

    def test_settings_context_extracts_values_from_setting_objects(self):
        """SettingsContext extracts 'value' from setting dicts."""
        # Replicate the SettingsContext class logic from research_service.py
        snapshot = {
            "llm.provider": {"value": "openai", "type": "string"},
            "search.tool": "google",  # plain value
        }

        # Simulate the extraction logic
        values = {}
        for key, setting in snapshot.items():
            if isinstance(setting, dict) and "value" in setting:
                values[key] = setting["value"]
            else:
                values[key] = setting

        assert values["llm.provider"] == "openai"
        assert values["search.tool"] == "google"

    def test_settings_context_get_setting_from_snapshot(self):
        """get_setting returns value from snapshot, default for missing."""
        values = {"llm.provider": "openai"}

        def get_setting(key, default=None):
            return values.get(key, default)

        assert get_setting("llm.provider") == "openai"
        assert get_setting("missing.key", "fallback") == "fallback"

    def test_settings_context_empty_snapshot(self):
        """Empty snapshot → all get_setting calls return default."""
        values = {}

        def get_setting(key, default=None):
            return values.get(key, default)

        assert get_setting("any.key", 42) == 42


class TestProgressCallback:
    """Tests for progress callback logic."""

    def test_progress_adjusted_for_detailed_output_generation(self):
        """Detailed mode output_generation → capped at 80."""
        mode = "detailed"
        metadata = {"phase": "output_generation"}
        progress_percent = 90

        adjusted_progress = progress_percent
        if mode == "detailed" and metadata.get("phase") == "output_generation":
            adjusted_progress = min(80, progress_percent)

        assert adjusted_progress == 80

    def test_progress_adjusted_for_detailed_report_generation(self):
        """Detailed mode report_generation → scaled 80-95%."""
        mode = "detailed"
        metadata = {"phase": "report_generation"}
        progress_percent = 50

        adjusted_progress = progress_percent
        if mode == "detailed" and metadata.get("phase") == "report_generation":
            if progress_percent is not None:
                normalized = progress_percent / 100
                adjusted_progress = 80 + (normalized * 15)

        assert adjusted_progress == 87.5

    def test_progress_adjusted_for_quick_output_generation(self):
        """Quick mode output_generation → at least 85%, scaled 85-95%."""
        mode = "quick"
        metadata = {"phase": "output_generation"}
        progress_percent = 50

        adjusted_progress = progress_percent
        if mode == "quick" and metadata.get("phase") == "output_generation":
            adjusted_progress = max(85, progress_percent)
            if progress_percent is not None and progress_percent > 0:
                normalized = progress_percent / 100
                adjusted_progress = 85 + (normalized * 10)

        assert adjusted_progress == 90.0

    def test_search_plan_extracted_from_message(self):
        """SEARCH_PLAN: in message → engines extracted."""
        message = "Planning SEARCH_PLAN: google, bing, wikipedia"
        metadata = {}

        if "SEARCH_PLAN:" in message:
            engines = message.split("SEARCH_PLAN:")[1].strip()
            metadata["planned_engines"] = engines
            metadata["phase"] = "search_planning"

        assert metadata["planned_engines"] == "google, bing, wikipedia"
        assert metadata["phase"] == "search_planning"

    def test_engine_selected_extracted_from_message(self):
        """ENGINE_SELECTED: in message → engine extracted."""
        message = "Selected ENGINE_SELECTED: google"
        metadata = {}

        if "ENGINE_SELECTED:" in message:
            engine = message.split("ENGINE_SELECTED:")[1].strip()
            metadata["selected_engine"] = engine
            metadata["phase"] = "search"

        assert metadata["selected_engine"] == "google"


class TestTerminationHandling:
    """Tests for termination checks in research process."""

    def test_termination_during_progress_raises(self):
        """Termination requested during progress → raises exception."""
        from local_deep_research.web.services.research_service import (
            ResearchTerminatedException,
        )

        with pytest.raises(ResearchTerminatedException):
            raise ResearchTerminatedException("Research was terminated by user")


def _classify_error(error_message):
    """Replicate error classification logic from run_research_process (lines 714-733)."""
    if "status code: 503" in error_message:
        return "ollama_unavailable"
    if "status code: 404" in error_message:
        return "model_not_found"
    if "status code:" in error_message:
        return "api_error"
    if "connection" in error_message.lower():
        return "connection_error"
    return "unknown"


class TestErrorClassification:
    """Tests for search error classification in run_research_process."""

    @pytest.mark.parametrize(
        "error_message, expected_type",
        [
            ("Request failed with status code: 503", "ollama_unavailable"),
            ("status code: 404 not found", "model_not_found"),
            ("status code: 429 rate limited", "api_error"),
            ("status code: 500 internal error", "api_error"),
            ("Connection refused to localhost:11434", "connection_error"),
            ("TCP connection reset by peer", "connection_error"),
            ("Something unexpected happened", "unknown"),
            ("", "unknown"),
        ],
    )
    def test_error_classification(self, error_message, expected_type):
        assert _classify_error(error_message) == expected_type

    def test_503_takes_priority_over_generic_status_code(self):
        """503 is matched specifically before generic 'status code:' pattern."""
        assert _classify_error("status code: 503") == "ollama_unavailable"

    def test_404_takes_priority_over_generic_status_code(self):
        """404 is matched specifically before generic 'status code:' pattern."""
        assert _classify_error("status code: 404") == "model_not_found"


class TestResearchModes:
    """Tests for mode-specific behavior."""

    def test_quick_mode_checks_findings(self):
        """Quick mode checks for findings or formatted_findings."""
        results = {"findings": ["f1"], "formatted_findings": "Summary text"}
        mode = "quick"

        if mode == "quick":
            has_findings = bool(
                results.get("findings") or results.get("formatted_findings")
            )
        else:
            has_findings = False

        assert has_findings is True

    def test_quick_mode_detects_error_in_findings(self):
        """Quick mode detects 'Error:' prefix in formatted_findings."""
        results = {"formatted_findings": "Error: token limit exceeded"}

        raw = results["formatted_findings"]
        is_error = isinstance(raw, str) and raw.startswith("Error:")

        assert is_error is True

    def test_quick_mode_no_error_prefix(self):
        """Normal formatted_findings → not detected as error."""
        results = {"formatted_findings": "This is a normal summary."}

        raw = results["formatted_findings"]
        is_error = isinstance(raw, str) and raw.startswith("Error:")

        assert is_error is False
