"""
Tests for advanced_search_system/filters/followup_relevance_filter.py

Tests cover:
- FollowUpRelevanceFilter initialization
- filter_results method
- _select_relevant_sources method
"""

from unittest.mock import Mock


class TestFollowUpRelevanceFilterInit:
    """Tests for FollowUpRelevanceFilter initialization."""

    def test_inherits_from_base_filter(self):
        """Test that FollowUpRelevanceFilter inherits from BaseFilter."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )
        from local_deep_research.advanced_search_system.filters.base_filter import (
            BaseFilter,
        )

        assert issubclass(FollowUpRelevanceFilter, BaseFilter)

    def test_init_with_model(self):
        """Test initialization with a model."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        filter_obj = FollowUpRelevanceFilter(model=mock_model)

        assert filter_obj.model is mock_model

    def test_init_without_model(self):
        """Test initialization without a model."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        filter_obj = FollowUpRelevanceFilter()

        assert filter_obj.model is None


class TestFilterResults:
    """Tests for FollowUpRelevanceFilter.filter_results method."""

    def test_empty_results_returns_empty_list(self):
        """Test that empty results returns empty list."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        filter_obj = FollowUpRelevanceFilter(model=Mock())

        result = filter_obj.filter_results([], "test query")

        assert result == []

    def test_filter_results_with_model(self):
        """Test filtering results with model."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0, 2]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        results = [
            {"title": "Result 1", "url": "http://example.com/1"},
            {"title": "Result 2", "url": "http://example.com/2"},
            {"title": "Result 3", "url": "http://example.com/3"},
        ]

        filtered = filter_obj.filter_results(results, "test query")

        assert len(filtered) == 2
        assert filtered[0]["title"] == "Result 1"
        assert filtered[1]["title"] == "Result 3"

    def test_filter_results_respects_max_results(self):
        """Test that max_results parameter is passed to selection."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0, 1, 2, 3, 4]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        results = [{"title": f"Result {i}"} for i in range(10)]

        filtered = filter_obj.filter_results(results, "query", max_results=5)

        assert len(filtered) == 5

    def test_filter_results_deduplicates_sources(self):
        """A repeated index must not return the same source twice."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[1, 1, 0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        results = [
            {"title": "Result 1"},
            {"title": "Result 2"},
            {"title": "Result 3"},
        ]

        filtered = filter_obj.filter_results(results, "test query")

        # Index 1 is repeated but its source appears once; first-seen order.
        assert filtered == [results[1], results[0]]

    def test_filter_results_with_past_findings(self):
        """Test filtering with past findings context."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[1]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        results = [
            {"title": "Result 1"},
            {"title": "Result 2"},
        ]

        filter_obj.filter_results(
            results, "query", past_findings="Previous research summary"
        )

        # Check that the prompt included past findings
        call_args = mock_model.invoke.call_args[0][0]
        assert "Previous research" in call_args

    def test_filter_results_with_original_query(self):
        """Test filtering with original query context."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        results = [{"title": "Result 1"}]

        filter_obj.filter_results(
            results, "follow-up query", original_query="original research query"
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "original research query" in call_args


class TestSelectRelevantSources:
    """Tests for FollowUpRelevanceFilter._select_relevant_sources method."""

    def test_no_model_returns_first_max_results(self):
        """Test that no model returns first max_results indices."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        filter_obj = FollowUpRelevanceFilter(model=None)
        sources = [{"title": f"Source {i}"} for i in range(10)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [0, 1, 2, 3, 4]

    def test_model_returns_parsed_indices(self):
        """Test that model response is parsed correctly."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[1, 3, 5]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(10)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [1, 3, 5]

    def test_handles_json_with_think_tags(self):
        """Test that think tags are removed from response."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="<think>reasoning</think>[0, 2]"
        )

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=3
        )

        assert indices == [0, 2]

    def test_handles_invalid_json_with_regex_fallback(self):
        """Test that invalid JSON falls back to regex extraction."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="I recommend sources 0, 2, and 4 as most relevant."
        )

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(10)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert 0 in indices
        assert 2 in indices
        assert 4 in indices

    def test_filters_out_indices_beyond_source_length(self):
        """Test that indices beyond source list are filtered out."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0, 1, 100, 200]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [0, 1]

    def test_handles_model_exception(self):
        """Test that model exception falls back to first max_results."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.side_effect = Exception("Model error")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(10)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=3
        )

        assert indices == [0, 1, 2]

    def test_builds_source_list_with_title_url_snippet(self):
        """Test that source list includes title, url, and snippet."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [
            {
                "title": "Test Title",
                "url": "http://example.com",
                "snippet": "Test snippet content",
            }
        ]

        filter_obj._select_relevant_sources(sources, "query", "", max_results=1)

        call_args = mock_model.invoke.call_args[0][0]
        assert "Test Title" in call_args
        assert "http://example.com" in call_args
        assert "Test snippet" in call_args

    def test_uses_content_preview_when_no_snippet(self):
        """Test that content_preview is used when snippet is missing."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [
            {
                "title": "Test",
                "content_preview": "Preview content here",
            }
        ]

        filter_obj._select_relevant_sources(sources, "query", "", max_results=1)

        call_args = mock_model.invoke.call_args[0][0]
        assert "Preview content" in call_args

    def test_truncates_long_snippets(self):
        """Test that long snippets are truncated to approximately 150 chars."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [
            {
                "title": "Test",
                "snippet": "A" * 200,
            }
        ]

        filter_obj._select_relevant_sources(sources, "query", "", max_results=1)

        call_args = mock_model.invoke.call_args[0][0]
        # Check that the snippet was truncated (not all 200 A's present)
        # Code does [:150] which results in roughly 150 chars
        assert call_args.count("A") < 200  # Definitely truncated from original

    def test_handles_float_indices(self):
        """Test that float indices are converted to int."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0.0, 2.0, 4.0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(10)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [0, 2, 4]

    def test_rejects_negative_indices(self):
        """Negative indices must be rejected, not select the last source."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[-1, 0, 2, -5]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        # -1 previously slipped through and selected sources[-1] (the last
        # source); it must now be dropped entirely.
        assert indices == [0, 2]

    def test_deduplicates_repeated_indices(self):
        """Repeated indices contribute one entry, preserving first-seen order."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0, 0, 2, 2, 1]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [0, 2, 1]

    def test_rejects_bool_indices(self):
        """Booleans (a subclass of int) must not be treated as indices."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[true, 0, 2]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        # `true` previously coerced to index 1; it must now be skipped.
        assert indices == [0, 2]

    def test_regex_fallback_deduplicates_indices(self):
        """The regex fallback path also deduplicates repeated indices."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="use sources 0, 0, and 2 (really, 0 and 2)"
        )

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=5
        )

        assert indices == [0, 2]

    def test_handles_response_not_a_list(self):
        """Test handling when response is not a list."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content='{"result": [0, 1]}')

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": f"Source {i}"} for i in range(5)]

        # Should fall back to regex extraction
        indices = filter_obj._select_relevant_sources(
            sources, "query", "", max_results=3
        )

        assert 0 in indices
        assert 1 in indices


class TestContextInPrompt:
    """Tests for context inclusion in prompts."""

    def test_includes_original_query_in_context(self):
        """Test that original query is included in context section."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": "Source 1"}]

        filter_obj._select_relevant_sources(
            sources,
            "follow-up query",
            "",
            max_results=1,
            original_query="original",
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "Original research question: original" in call_args

    def test_includes_past_findings_in_context(self):
        """Test that past findings are included in context section."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": "Source 1"}]

        filter_obj._select_relevant_sources(
            sources, "query", "Previous findings summary", max_results=1
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "Previous findings summary" in call_args

    def test_no_context_section_when_empty(self):
        """Test that context section is empty when no context provided."""
        from local_deep_research.advanced_search_system.filters.followup_relevance_filter import (
            FollowUpRelevanceFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[0]")

        filter_obj = FollowUpRelevanceFilter(model=mock_model)
        sources = [{"title": "Source 1"}]

        filter_obj._select_relevant_sources(
            sources, "query", "", max_results=1, original_query=""
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "Previous Research Context" not in call_args
