"""
Tests for pure logic functions in search strategies.

These functions have zero existing test coverage despite being
testable without mocking LLMs or search engines:

- SourceBasedSearchStrategy._format_search_results_as_context()
- AdaptiveDecompositionStrategy._calculate_confidence()
- AdaptiveDecompositionStrategy StepType enum and StepResult dataclass
- NewsAggregationStrategy._format_news_summary()
- NewsAggregationStrategy._fallback_news_extraction()
- BaseSearchStrategy.check_termination()
"""

from unittest.mock import Mock

import pytest


# ---------------------------------------------------------------------------
# StepType and StepResult (adaptive_decomposition_strategy.py)
# ---------------------------------------------------------------------------


class TestStepType:
    """Tests for StepType enum values."""

    def test_all_step_types_exist(self):
        """All expected step types exist."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepType,
        )

        assert StepType.CONSTRAINT_EXTRACTION.value == "constraint_extraction"
        assert StepType.INITIAL_SEARCH.value == "initial_search"
        assert StepType.VERIFICATION.value == "verification"
        assert StepType.REFINEMENT.value == "refinement"
        assert StepType.SYNTHESIS.value == "synthesis"

    def test_step_type_count(self):
        """Exactly 5 step types defined."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepType,
        )

        assert len(StepType) == 5

    def test_step_type_comparison(self):
        """Step types support equality comparison."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepType,
        )

        assert StepType.VERIFICATION == StepType.VERIFICATION
        assert StepType.VERIFICATION != StepType.REFINEMENT


class TestStepResult:
    """Tests for StepResult dataclass."""

    def test_create_with_required_fields(self):
        """Create StepResult with required fields."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepResult,
            StepType,
        )

        result = StepResult(
            step_type=StepType.INITIAL_SEARCH,
            description="Initial search",
            findings={"data": [1, 2, 3]},
        )

        assert result.step_type == StepType.INITIAL_SEARCH
        assert result.description == "Initial search"
        assert result.findings == {"data": [1, 2, 3]}

    def test_default_values(self):
        """Default values for optional fields."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepResult,
            StepType,
        )

        result = StepResult(
            step_type=StepType.SYNTHESIS,
            description="Synthesize",
            findings={},
        )

        assert result.next_action is None
        assert result.confidence == 0.0

    def test_create_with_all_fields(self):
        """Create StepResult with all fields."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            StepResult,
            StepType,
        )

        result = StepResult(
            step_type=StepType.VERIFICATION,
            description="Verify facts",
            findings={"verified": True},
            next_action="refine",
            confidence=0.85,
        )

        assert result.next_action == "refine"
        assert result.confidence == 0.85


# ---------------------------------------------------------------------------
# _calculate_confidence (adaptive_decomposition_strategy.py)
# ---------------------------------------------------------------------------


class TestCalculateConfidence:
    """Tests for AdaptiveDecompositionStrategy._calculate_confidence()."""

    def _make_strategy(self, constraints, verified_facts, candidates=None):
        """Create a strategy with specific working_knowledge state."""
        from local_deep_research.advanced_search_system.strategies.adaptive_decomposition_strategy import (
            AdaptiveDecompositionStrategy,
        )

        model = Mock()
        search = Mock()
        strategy = AdaptiveDecompositionStrategy(
            model=model, search=search, all_links_of_system=[]
        )
        strategy.working_knowledge = {
            "original_query": "test",
            "constraints": constraints,
            "candidates": candidates or [],
            "verified_facts": verified_facts,
            "uncertainties": [],
        }
        return strategy

    def test_no_constraints_returns_zero(self):
        """Zero constraints returns 0.0 confidence."""
        strategy = self._make_strategy(constraints=[], verified_facts=["fact1"])
        assert strategy._calculate_confidence() == 0.0

    def test_all_constraints_verified(self):
        """All constraints verified gives high confidence."""
        strategy = self._make_strategy(
            constraints=["c1", "c2"],
            verified_facts=["f1", "f2"],
        )
        confidence = strategy._calculate_confidence()
        assert confidence == pytest.approx(0.95)  # Capped at 0.95

    def test_half_constraints_verified(self):
        """Half constraints verified gives 0.5."""
        strategy = self._make_strategy(
            constraints=["c1", "c2"],
            verified_facts=["f1"],
        )
        confidence = strategy._calculate_confidence()
        assert confidence == pytest.approx(0.5)

    def test_candidates_boost(self):
        """Having candidates adds 0.1 to confidence."""
        strategy = self._make_strategy(
            constraints=["c1", "c2"],
            verified_facts=["f1"],
            candidates=["candidate_a"],
        )
        confidence = strategy._calculate_confidence()
        assert confidence == pytest.approx(0.6)  # 0.5 + 0.1

    def test_confidence_capped_at_095(self):
        """Confidence capped at 0.95 even with candidates boost."""
        strategy = self._make_strategy(
            constraints=["c1"],
            verified_facts=["f1"],
            candidates=["c_a"],
        )
        confidence = strategy._calculate_confidence()
        # 1.0 + 0.1 = 1.1, capped to 0.95
        assert confidence == pytest.approx(0.95)

    def test_no_verified_facts(self):
        """No verified facts gives 0.0 confidence."""
        strategy = self._make_strategy(
            constraints=["c1", "c2", "c3"],
            verified_facts=[],
        )
        assert strategy._calculate_confidence() == pytest.approx(0.0)

    def test_more_verified_than_constraints(self):
        """More verified facts than constraints still capped at 0.95."""
        strategy = self._make_strategy(
            constraints=["c1"],
            verified_facts=["f1", "f2", "f3"],
        )
        confidence = strategy._calculate_confidence()
        # 3/1 = 3.0, capped to 0.95
        assert confidence == pytest.approx(0.95)


# ---------------------------------------------------------------------------
# _format_search_results_as_context (source_based_strategy.py)
# ---------------------------------------------------------------------------


class TestFormatSearchResultsAsContext:
    """Tests for SourceBasedSearchStrategy._format_search_results_as_context()."""

    def _make_strategy(self):
        """Create a SourceBasedSearchStrategy with mocked dependencies."""
        from local_deep_research.advanced_search_system.strategies.source_based_strategy import (
            SourceBasedSearchStrategy,
        )

        model = Mock()
        search = Mock()
        strategy = SourceBasedSearchStrategy(
            model=model, search=search, all_links_of_system=[]
        )
        return strategy

    def test_empty_results(self):
        """Empty results list produces empty string."""
        strategy = self._make_strategy()
        result = strategy._format_search_results_as_context([])
        assert result == ""

    def test_single_result(self):
        """Single result formatted correctly."""
        strategy = self._make_strategy()
        results = [
            {
                "title": "Python Docs",
                "snippet": "Official documentation",
                "link": "https://docs.python.org",
            }
        ]
        result = strategy._format_search_results_as_context(results)
        assert "Source 1: Python Docs" in result
        assert "URL: https://docs.python.org" in result
        assert "Snippet: Official documentation" in result

    def test_multiple_results_numbered(self):
        """Multiple results numbered sequentially."""
        strategy = self._make_strategy()
        results = [
            {"title": "First", "snippet": "A", "link": "https://a.com"},
            {"title": "Second", "snippet": "B", "link": "https://b.com"},
            {"title": "Third", "snippet": "C", "link": "https://c.com"},
        ]
        result = strategy._format_search_results_as_context(results)
        assert "Source 1: First" in result
        assert "Source 2: Second" in result
        assert "Source 3: Third" in result

    def test_missing_title_uses_untitled(self):
        """Missing title defaults to 'Untitled'."""
        strategy = self._make_strategy()
        results = [{"snippet": "Content", "link": "https://example.com"}]
        result = strategy._format_search_results_as_context(results)
        assert "Source 1: Untitled" in result

    def test_missing_snippet_skips_result(self):
        """Result with empty snippet is skipped from output."""
        strategy = self._make_strategy()
        results = [
            {"title": "No Snippet", "snippet": "", "link": "https://a.com"},
            {
                "title": "Has Snippet",
                "snippet": "Content",
                "link": "https://b.com",
            },
        ]
        result = strategy._format_search_results_as_context(results)
        assert "No Snippet" not in result
        # Numbering uses original enumerate index, so second result is Source 2
        assert "Source 2: Has Snippet" in result

    def test_missing_link_uses_empty(self):
        """Missing link defaults to empty string."""
        strategy = self._make_strategy()
        results = [{"title": "Test", "snippet": "Content"}]
        result = strategy._format_search_results_as_context(results)
        assert "URL: \n" in result

    def test_results_separated_by_double_newline(self):
        """Results are separated by double newlines."""
        strategy = self._make_strategy()
        results = [
            {"title": "A", "snippet": "Content A", "link": "https://a.com"},
            {"title": "B", "snippet": "Content B", "link": "https://b.com"},
        ]
        result = strategy._format_search_results_as_context(results)
        assert "\n\n" in result

    def test_does_not_modify_input(self):
        """Method does not modify the input list or dicts."""
        strategy = self._make_strategy()
        results = [
            {"title": "Test", "snippet": "Content", "link": "https://a.com"}
        ]
        original_result = dict(results[0])
        strategy._format_search_results_as_context(results)
        assert results[0] == original_result


# ---------------------------------------------------------------------------
# _format_news_summary (news_strategy.py)
# ---------------------------------------------------------------------------


class TestFormatNewsSummary:
    """Tests for NewsAggregationStrategy._format_news_summary()."""

    def _make_strategy(self):
        """Create a NewsAggregationStrategy with mocked dependencies."""
        from local_deep_research.advanced_search_system.strategies.news_strategy import (
            NewsAggregationStrategy,
        )

        model = Mock()
        search = Mock()
        strategy = NewsAggregationStrategy(
            model=model, search=search, all_links_of_system=[]
        )
        return strategy

    def test_empty_items_returns_no_stories(self):
        """Empty list returns 'no stories' message."""
        strategy = self._make_strategy()
        result = strategy._format_news_summary([])
        assert "No significant news stories found" in result

    def test_single_item_formatted(self):
        """Single news item is formatted with headline and impact."""
        strategy = self._make_strategy()
        items = [
            {
                "headline": "Major Event",
                "category": "Politics",
                "impact_score": 8,
                "summary": "Something happened.",
            }
        ]
        result = strategy._format_news_summary(items)
        assert "Major Event" in result
        assert "8/10" in result
        assert "Politics" in result

    def test_groups_by_category(self):
        """Items are grouped by category."""
        strategy = self._make_strategy()
        items = [
            {"headline": "Tech News", "category": "Tech", "impact_score": 7},
            {"headline": "War Update", "category": "War", "impact_score": 9},
            {"headline": "More Tech", "category": "Tech", "impact_score": 6},
        ]
        result = strategy._format_news_summary(items)
        assert "**Tech** (2 stories)" in result
        assert "**War** (1 stories)" in result

    def test_top_story_highlighted(self):
        """Top story (highest impact) is highlighted."""
        strategy = self._make_strategy()
        items = [
            {"headline": "Minor Event", "category": "Other", "impact_score": 3},
            {
                "headline": "Major Event",
                "category": "War",
                "impact_score": 10,
                "summary": "Big thing happened.",
            },
        ]
        result = strategy._format_news_summary(items)
        assert "**Top Story**: Major Event" in result
        assert "Big thing happened." in result

    def test_max_three_per_category(self):
        """At most 3 items shown per category."""
        strategy = self._make_strategy()
        items = [
            {"headline": f"Item {i}", "category": "Tech", "impact_score": i}
            for i in range(5)
        ]
        result = strategy._format_news_summary(items)
        # Count lines starting with "- " under Tech
        tech_items = [
            line for line in result.split("\n") if line.startswith("- Item")
        ]
        assert len(tech_items) == 3

    def test_missing_impact_score_shows_na(self):
        """Missing impact_score shows N/A."""
        strategy = self._make_strategy()
        items = [
            {"headline": "No Score", "category": "Other"},
        ]
        result = strategy._format_news_summary(items)
        assert "N/A/10" in result

    def test_top_story_missing_summary(self):
        """Top story with missing summary shows fallback."""
        strategy = self._make_strategy()
        items = [
            {"headline": "No Summary", "category": "Other", "impact_score": 5},
        ]
        result = strategy._format_news_summary(items)
        assert "No summary available" in result

    def test_total_count_in_header(self):
        """Header includes total story count."""
        strategy = self._make_strategy()
        items = [
            {"headline": f"Story {i}", "category": "Other", "impact_score": i}
            for i in range(4)
        ]
        result = strategy._format_news_summary(items)
        assert "Found 4 significant news stories" in result


# ---------------------------------------------------------------------------
# _fallback_news_extraction (news_strategy.py)
# ---------------------------------------------------------------------------


class TestFallbackNewsExtraction:
    """Tests for NewsAggregationStrategy._fallback_news_extraction()."""

    def _make_strategy(self):
        from local_deep_research.advanced_search_system.strategies.news_strategy import (
            NewsAggregationStrategy,
        )

        model = Mock()
        search = Mock()
        return NewsAggregationStrategy(
            model=model, search=search, all_links_of_system=[]
        )

    def test_empty_snippets(self):
        """Empty snippets produces empty result."""
        strategy = self._make_strategy()
        result = strategy._fallback_news_extraction([])
        assert result["status"] == "Fallback extraction"
        assert result["news_items"] == []
        assert "0 news stories" in result["answer"]

    def test_extracts_from_valid_snippets(self):
        """Extracts news items from snippets with valid titles."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": 1,
                "title": "Major breakthrough in AI research announced today",
                "url": "https://example.com/ai",
                "snippet": "Scientists announced...",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert len(result["news_items"]) == 1
        item = result["news_items"][0]
        assert item["category"] == "Other"
        assert item["impact_score"] == 5
        assert item["source_url"] == "https://example.com/ai"

    def test_skips_short_titles(self):
        """Titles <= 10 characters are skipped."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": 1,
                "title": "Short",
                "url": "https://a.com",
                "snippet": "s",
                "content": "",
            },
            {
                "id": 2,
                "title": "1234567890",
                "url": "https://b.com",
                "snippet": "s",
                "content": "",
            },
            {
                "id": 3,
                "title": "Long enough title for news",
                "url": "https://c.com",
                "snippet": "ok",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert len(result["news_items"]) == 1
        assert result["news_items"][0]["source_url"] == "https://c.com"

    def test_skips_empty_titles(self):
        """Empty titles are skipped."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": 1,
                "title": "",
                "url": "https://a.com",
                "snippet": "content",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert len(result["news_items"]) == 0

    def test_truncates_long_headlines(self):
        """Headlines are truncated to 60 characters."""
        strategy = self._make_strategy()
        long_title = "A" * 100
        snippets = [
            {
                "id": 1,
                "title": long_title,
                "url": "https://a.com",
                "snippet": "s",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert len(result["news_items"][0]["headline"]) == 60

    def test_max_ten_items(self):
        """At most 10 items extracted."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": i,
                "title": f"Valid news headline number {i} here",
                "url": f"https://example{i}.com",
                "snippet": "content",
                "content": "",
            }
            for i in range(20)
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert len(result["news_items"]) == 10

    def test_uses_snippet_for_summary(self):
        """Snippet is used as summary."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": 1,
                "title": "Breaking news about something important",
                "url": "https://a.com",
                "snippet": "Detailed snippet content",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert result["news_items"][0]["summary"] == "Detailed snippet content"

    def test_fallback_summary_when_no_snippet(self):
        """Falls back to default summary when no snippet."""
        strategy = self._make_strategy()
        snippets = [
            {
                "id": 1,
                "title": "Breaking news about something important",
                "url": "https://a.com",
                "snippet": "",
                "content": "",
            },
        ]
        result = strategy._fallback_news_extraction(snippets)
        assert result["news_items"][0]["summary"] == "No summary available"


# ---------------------------------------------------------------------------
# BaseSearchStrategy.check_termination() (base_strategy.py)
# ---------------------------------------------------------------------------


class TestCheckTermination:
    """Tests for BaseSearchStrategy.check_termination() method.

    This method was untested in the existing test_base_strategy.py.
    """

    def _make_strategy(self):
        from local_deep_research.advanced_search_system.strategies.base_strategy import (
            BaseSearchStrategy,
        )

        class ConcreteStrategy(BaseSearchStrategy):
            def analyze_topic(self, query):
                return {}

        return ConcreteStrategy()

    def test_check_termination_calls_callback(self):
        """check_termination() calls progress_callback with termination_check phase."""
        strategy = self._make_strategy()
        callback = Mock()
        strategy.set_progress_callback(callback)

        strategy.check_termination()

        callback.assert_called_once()
        args = callback.call_args[0]
        assert args[2]["phase"] == "termination_check"

    def test_check_termination_without_callback(self):
        """check_termination() does nothing without callback."""
        strategy = self._make_strategy()
        # Should not raise
        strategy.check_termination()

    def test_check_termination_passes_none_progress(self):
        """check_termination() passes None as progress_percent."""
        strategy = self._make_strategy()
        callback = Mock()
        strategy.set_progress_callback(callback)

        strategy.check_termination()

        args = callback.call_args[0]
        assert args[1] is None
