"""
Integration tests for CrossEngineFilter.

Tests cover:
- CrossEngineFilter initialization with various settings
- filter_results() with actual class and mocked LLM
- JSON parsing failures from malformed LLM responses
- Think tag removal from responses
- Settings snapshot handling
- Edge cases with actual class instantiation
"""

from unittest.mock import Mock, patch


class TestCrossEngineFilterSettingsIntegration:
    """Integration tests for CrossEngineFilter settings handling."""

    def test_init_with_explicit_max_results(self):
        """Should use explicit max_results when provided."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        filter_instance = CrossEngineFilter(model=Mock(), max_results=75)

        assert filter_instance.max_results == 75

    def test_init_with_settings_snapshot_explicit_max(self):
        """Settings snapshot should be passed but explicit max_results takes precedence."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        snapshot = {"search.cross_engine_max_results": 50}

        # When max_results is explicitly provided, it should be used
        filter_instance = CrossEngineFilter(
            model=Mock(), max_results=75, settings_snapshot=snapshot
        )

        assert filter_instance.max_results == 75

    def test_init_default_max_results_fallback(self):
        """Should use default 100 when max_results=None and no valid settings."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        # Patch at config.thread_settings level which is where it's imported from
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get:
            mock_get.return_value = None

            filter_instance = CrossEngineFilter(model=Mock())

            # Falls back to 100
            assert filter_instance.max_results == 100

    def test_init_integer_conversion(self):
        """Should convert string max_results to integer."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        # Test the integer conversion logic by passing an integer directly
        filter_instance = CrossEngineFilter(model=Mock(), max_results=50)

        assert filter_instance.max_results == 50
        assert isinstance(filter_instance.max_results, int)


class TestCrossEngineFilterThinkTagHandling:
    """Tests for think tag removal from LLM responses."""

    def test_think_tags_removed_from_response(self):
        """Should remove <think> tags from LLM response."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        # Response with think tags (like Claude's extended thinking)
        mock_model.invoke.return_value = Mock(
            content="<think>Let me analyze these results...</think>\n[2, 0, 1, 3]"
        )

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        # Should have parsed the array after removing think tags
        assert len(filtered) == 4  # Indices [2, 0, 1, 3]
        assert filtered[0]["title"] == "Result 2"

    def test_nested_think_tags_handled(self):
        """Should handle nested or multiple think tags."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="<think>First thought</think><think>Second thought</think>[0, 1]"
        )

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        assert len(filtered) == 2


class TestCrossEngineFilterJSONParsingEdgeCases:
    """Tests for JSON parsing edge cases with actual class."""

    def test_json_with_surrounding_text(self):
        """Should extract JSON array from text with surrounding content."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Based on my analysis, the ranked indices are: [5, 2, 8, 0] These are ordered by relevance."
        )

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        assert len(filtered) == 4
        assert filtered[0]["title"] == "Result 5"

    def test_json_with_newlines_and_formatting(self):
        """Should handle JSON array split across lines."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="""The ranked indices are:
[
  3,
  1,
  0
]
"""
        )

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

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

    def test_response_with_no_brackets(self):
        """Should return original results when no brackets found."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="I cannot determine the relevance order for these results."
        )

        filter_instance = CrossEngineFilter(model=mock_model, max_results=5)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        # Should return limited original results
        assert len(filtered) == 5
        assert filtered[0]["title"] == "Result 0"

    def test_response_with_mismatched_brackets(self):
        """Should handle mismatched brackets gracefully."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=5)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

        # Should not raise, returns original results
        filtered = filter_instance.filter_results(results, "test query")

        assert len(filtered) == 5

    def test_response_with_invalid_json_content(self):
        """Should handle invalid JSON inside brackets."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="[1, two, 3]")  # Invalid

        filter_instance = CrossEngineFilter(model=mock_model, max_results=5)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

        # Should not raise
        filtered = filter_instance.filter_results(results, "test query")

        assert len(filtered) == 5


class TestCrossEngineFilterResponseFormats:
    """Tests for different LLM response formats."""

    def test_response_without_content_attribute(self):
        """Should handle response without content attribute."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        # Response that's just a string (no content attribute)
        mock_response = "[0, 1, 2]"
        mock_model.invoke.return_value = mock_response

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        assert len(filtered) == 3

    def test_response_with_empty_content(self):
        """Should handle empty content gracefully."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="")

        filter_instance = CrossEngineFilter(model=mock_model, max_results=5)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        # Returns limited original results
        assert len(filtered) == 5


class TestCrossEngineFilterIndexValidation:
    """Tests for index validation during filtering."""

    def test_out_of_range_indices_ignored(self):
        """Should ignore indices that are out of range."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        # Indices 100 and 999 are out of range for 15 results
        mock_model.invoke.return_value = Mock(content="[0, 100, 1, 999, 2]")

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        # Only valid indices [0, 1, 2] should be included
        assert len(filtered) == 3
        assert filtered[0]["title"] == "Result 0"
        assert filtered[1]["title"] == "Result 1"
        assert filtered[2]["title"] == "Result 2"

    def test_negative_indices_in_response(self):
        """Should handle negative indices in response."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(15)
        ]

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

        # Negative indices are technically valid in Python (wrap around)
        # but should be filtered as "out of range" conceptually
        # The current implementation doesn't explicitly check for negative,
        # so -1 would access last element. This tests current behavior.
        assert len(filtered) >= 3


class TestCrossEngineFilterReorderReindexCombinations:
    """Tests for different reorder/reindex combinations."""

    def test_reorder_true_reindex_true(self):
        """Both reorder and reindex should work together."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": "First", "snippet": "S1"},
            {"title": "Second", "snippet": "S2"},
            {"title": "Third", "snippet": "S3"},
        ] + [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(12)
        ]

        filtered = filter_instance.filter_results(
            results, "query", reorder=True, reindex=True
        )

        # Should be reordered
        assert filtered[0]["title"] == "Third"
        # Should be reindexed
        assert filtered[0]["index"] == "1"
        assert filtered[1]["index"] == "2"

    def test_reorder_false_reindex_true(self):
        """Should filter but not reorder, while still reindexing."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="[2, 0]"
        )  # Wants to reorder

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": "First", "snippet": "S1"},
            {"title": "Second", "snippet": "S2"},
            {"title": "Third", "snippet": "S3"},
        ] + [
            {"title": f"Result {i}", "snippet": f"Snippet {i}"}
            for i in range(12)
        ]

        filtered = filter_instance.filter_results(
            results, "query", reorder=False, reindex=True
        )

        # Should maintain original order (sorted indices: 0, 2)
        assert filtered[0]["title"] == "First"
        assert filtered[1]["title"] == "Third"
        # Should be reindexed
        assert filtered[0]["index"] == "1"
        assert filtered[1]["index"] == "2"

    def test_reorder_true_reindex_false(self):
        """Should reorder but not update indices."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {"title": "First", "snippet": "S1", "index": "old1"},
            {"title": "Second", "snippet": "S2", "index": "old2"},
            {"title": "Third", "snippet": "S3", "index": "old3"},
        ] + [
            {
                "title": f"Result {i}",
                "snippet": f"Snippet {i}",
                "index": f"old{i + 4}",
            }
            for i in range(12)
        ]

        filtered = filter_instance.filter_results(
            results, "query", reorder=True, reindex=False
        )

        # Should be reordered
        assert filtered[0]["title"] == "Third"
        # Should NOT be reindexed (keeps original indices)
        assert filtered[0]["index"] == "old3"


class TestCrossEngineFilterContextGeneration:
    """Tests for context generation passed to LLM."""

    def test_long_snippets_truncated(self):
        """Should truncate long snippets to 200 chars."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        long_snippet = "x" * 500
        results = [
            {"title": "Test", "snippet": long_snippet, "engine": "google"}
            for _ in range(15)
        ]

        filter_instance.filter_results(results, "query")

        # Check the prompt passed to invoke
        call_args = mock_model.invoke.call_args[0][0]
        # Should contain truncated snippet (200 chars + "...")
        assert "..." in call_args
        assert "x" * 500 not in call_args

    def test_max_30_results_in_context(self):
        """Should limit context to 30 results."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {
                "title": f"Result {i}",
                "snippet": f"Snippet {i}",
                "engine": "google",
            }
            for i in range(50)
        ]

        filter_instance.filter_results(results, "query")

        # Check prompt doesn't include result 35+
        call_args = mock_model.invoke.call_args[0][0]
        assert "[30]" not in call_args or "[35]" not in call_args

    def test_missing_fields_use_defaults(self):
        """Should handle missing title/snippet/engine gracefully."""
        from local_deep_research.advanced_search_system.filters.cross_engine_filter import (
            CrossEngineFilter,
        )

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

        filter_instance = CrossEngineFilter(model=mock_model, max_results=100)

        results = [
            {},  # Completely empty result
            {"title": "Only Title"},  # Missing snippet and engine
            {"snippet": "Only Snippet"},  # Missing title and engine
        ] + [{"title": f"Result {i}"} for i in range(12)]

        # Should not raise
        filtered = filter_instance.filter_results(results, "query")

        assert len(filtered) == 1

        # Check defaults were used in prompt
        call_args = mock_model.invoke.call_args[0][0]
        assert "Untitled" in call_args
        assert "Unknown engine" in call_args
