"""
Extended tests for CrossEngineFilter - Cross-engine search result filtering.

Tests cover:
- Filter initialization with settings
- Result filtering and ranking
- Reordering and reindexing logic
- LLM response parsing
- Edge cases and error handling
"""

import json


class TestCrossEngineFilterInitialization:
    """Tests for CrossEngineFilter initialization."""

    def test_default_max_results(self):
        """Default max_results should be 100."""
        default_max = 100
        assert default_max == 100

    def test_custom_max_results(self):
        """Custom max_results should be respected."""
        config = {"max_results": 50}
        assert config["max_results"] == 50

    def test_default_reorder_true(self):
        """Default reorder should be True."""
        default_reorder = True
        assert default_reorder is True

    def test_default_reindex_true(self):
        """Default reindex should be True."""
        default_reindex = True
        assert default_reindex is True

    def test_max_results_from_settings(self):
        """Should get max_results from settings if not provided."""
        settings_value = 75

        config = {"max_results": settings_value}
        assert config["max_results"] == 75


class TestResultFiltering:
    """Tests for result filtering logic."""

    def test_few_results_not_filtered(self):
        """Should not filter if 10 or fewer results."""
        results = [{"title": f"Result {i}"} for i in range(8)]
        max_results = 100

        # Simulating the condition in filter_results
        if len(results) <= 10:
            filtered = results[: min(max_results, len(results))]

        assert len(filtered) == 8

    def test_results_limited_to_max(self):
        """Should limit results to max_results."""
        results = [{"title": f"Result {i}"} for i in range(150)]
        max_results = 100

        limited = results[:max_results]

        assert len(limited) == 100

    def test_empty_results_handled(self):
        """Should handle empty results gracefully."""
        results = []
        max_results = 100

        filtered = results[: min(max_results, len(results))]

        assert filtered == []

    def test_results_preserve_structure(self):
        """Filtered results should preserve original structure."""
        results = [
            {
                "title": "Test",
                "snippet": "Snippet",
                "engine": "google",
                "url": "http://test.com",
            }
        ]
        max_results = 100

        filtered = results[: min(max_results, len(results))]

        assert filtered[0]["title"] == "Test"
        assert filtered[0]["engine"] == "google"


class TestReindexing:
    """Tests for result reindexing."""

    def test_reindex_updates_indices(self):
        """Reindexing should update result indices."""
        results = [{"title": f"Result {i}"} for i in range(5)]
        start_index = 0

        for i, result in enumerate(results):
            result["index"] = str(i + start_index + 1)

        assert results[0]["index"] == "1"
        assert results[4]["index"] == "5"

    def test_reindex_with_start_index(self):
        """Reindexing should respect start_index."""
        results = [{"title": f"Result {i}"} for i in range(3)]
        start_index = 10

        for i, result in enumerate(results):
            result["index"] = str(i + start_index + 1)

        assert results[0]["index"] == "11"
        assert results[2]["index"] == "13"

    def test_reindex_false_no_update(self):
        """When reindex=False, indices should not be updated."""
        results = [{"title": f"Result {i}", "index": "old"} for i in range(3)]
        reindex = False

        if reindex:
            for i, result in enumerate(results):
                result["index"] = str(i + 1)

        assert results[0]["index"] == "old"


class TestReordering:
    """Tests for result reordering."""

    def test_reorder_by_indices(self):
        """Should reorder results by ranked indices."""
        results = [
            {"title": "A", "original_index": 0},
            {"title": "B", "original_index": 1},
            {"title": "C", "original_index": 2},
        ]
        ranked_indices = [2, 0, 1]

        reordered = [
            results[idx] for idx in ranked_indices if idx < len(results)
        ]

        assert reordered[0]["title"] == "C"
        assert reordered[1]["title"] == "A"
        assert reordered[2]["title"] == "B"

    def test_reorder_false_maintains_original_order(self):
        """When reorder=False, should maintain original order."""
        results = [
            {"title": "A"},
            {"title": "B"},
            {"title": "C"},
        ]
        ranked_indices = [2, 0, 1]
        reorder = False

        if reorder:
            filtered = [results[idx] for idx in ranked_indices]
        else:
            filtered = [results[idx] for idx in sorted(ranked_indices)]

        assert filtered[0]["title"] == "A"
        assert filtered[1]["title"] == "B"
        assert filtered[2]["title"] == "C"

    def test_invalid_indices_skipped(self):
        """Should skip indices that are out of range."""
        results = [{"title": "A"}, {"title": "B"}]
        ranked_indices = [0, 1, 5, 10]  # 5 and 10 are invalid

        reordered = [
            results[idx] for idx in ranked_indices if idx < len(results)
        ]

        assert len(reordered) == 2


class TestPreviewContextGeneration:
    """Tests for preview context generation."""

    def test_preview_context_format(self):
        """Preview context should have expected format."""
        results = [
            {
                "title": "Test Title",
                "snippet": "Test snippet",
                "engine": "google",
            }
        ]

        preview_context = []
        for i, result in enumerate(results):
            title = result.get("title", "Untitled").strip()
            snippet = result.get("snippet", "").strip()
            engine = result.get("engine", "Unknown engine")

            preview_context.append(
                f"[{i}] Engine: {engine} | Title: {title}\nSnippet: {snippet}"
            )

        assert "[0]" in preview_context[0]
        assert "Engine: google" in preview_context[0]
        assert "Test Title" in preview_context[0]

    def test_snippet_truncation(self):
        """Long snippets should be truncated to 200 chars."""
        long_snippet = "x" * 300
        max_length = 200

        if len(long_snippet) > max_length:
            truncated = long_snippet[:max_length] + "..."

        assert len(truncated) == 203

    def test_context_limited_to_30_items(self):
        """Context should be limited to 30 items."""
        results = [{"title": f"Result {i}"} for i in range(50)]
        max_context_items = min(30, len(results))

        limited = results[:max_context_items]

        assert len(limited) == 30


class TestLLMResponseParsing:
    """Tests for LLM response parsing."""

    def test_parse_json_array_response(self):
        """Should parse JSON array from response."""
        response_text = "Here are the ranked results: [3, 0, 7, 1]"

        start_idx = response_text.find("[")
        end_idx = response_text.rfind("]")

        if start_idx >= 0 and end_idx > start_idx:
            array_text = response_text[start_idx : end_idx + 1]
            ranked_indices = json.loads(array_text)

        assert ranked_indices == [3, 0, 7, 1]

    def test_parse_empty_array_response(self):
        """Should parse empty array response."""
        response_text = "No relevant results found: []"

        start_idx = response_text.find("[")
        end_idx = response_text.rfind("]")

        if start_idx >= 0 and end_idx > start_idx:
            array_text = response_text[start_idx : end_idx + 1]
            ranked_indices = json.loads(array_text)

        assert ranked_indices == []

    def test_no_array_returns_original(self):
        """Should return original if no array found."""
        response_text = "I cannot determine relevance"

        start_idx = response_text.find("[")
        end_idx = response_text.rfind("]")

        if start_idx >= 0 and end_idx > start_idx:
            found_array = True
        else:
            found_array = False

        assert found_array is False

    def test_malformed_json_handled(self):
        """Should handle malformed JSON gracefully."""
        response_text = "[1, 2, 3"  # Missing closing bracket

        start_idx = response_text.find("[")
        end_idx = response_text.rfind("]")

        if start_idx >= 0 and end_idx > start_idx:
            try:
                array_text = response_text[start_idx : end_idx + 1]
                ranked_indices = json.loads(array_text)
            except json.JSONDecodeError:
                ranked_indices = None
        else:
            ranked_indices = None

        assert ranked_indices is None


class TestEmptyResultsHandling:
    """Tests for empty results handling."""

    def test_all_results_filtered_returns_top_10(self):
        """If all filtered, should return top 10 originals."""
        results = [{"title": f"Result {i}"} for i in range(20)]
        ranked_results = []  # All filtered

        if not ranked_results and results:
            top_results = results[: min(10, len(results))]
        else:
            top_results = ranked_results

        assert len(top_results) == 10

    def test_no_model_returns_limited_results(self):
        """Without model, should return limited results."""
        results = [{"title": f"Result {i}"} for i in range(150)]
        model = None
        max_results = 100

        if not model:
            filtered = results[: min(max_results, len(results))]

        assert len(filtered) == 100


class TestPromptConstruction:
    """Tests for LLM prompt construction."""

    def test_prompt_includes_query(self):
        """Prompt should include the original query."""
        query = "machine learning algorithms"
        prompt = f'Query: "{query}"'

        assert "machine learning algorithms" in prompt

    def test_prompt_includes_result_context(self):
        """Prompt should include result context."""
        results = [{"title": "Test", "snippet": "Snippet", "engine": "google"}]
        context = "\n\n".join(
            [
                f"[{i}] {r['engine']} | {r['title']}"
                for i, r in enumerate(results)
            ]
        )

        prompt = f"Search Results:\n{context}"

        assert "Test" in prompt
        assert "google" in prompt

    def test_prompt_requests_json_array(self):
        """Prompt should request JSON array response."""
        prompt = "Return the search results as a JSON array of indices"

        assert "JSON array" in prompt


class TestErrorHandling:
    """Tests for error handling."""

    def test_exception_returns_limited_results(self):
        """On exception, should return limited results."""
        results = [{"title": f"Result {i}"} for i in range(50)]
        max_results = 100

        try:
            raise Exception("LLM error")
        except Exception:
            top_results = results[: min(max_results, len(results))]

        assert len(top_results) == 50

    def test_reindex_on_error_path(self):
        """Should still reindex on error path if requested."""
        results = [{"title": f"Result {i}"} for i in range(5)]
        reindex = True
        start_index = 0

        try:
            raise Exception("Error")
        except Exception:
            if reindex:
                for i, result in enumerate(results):
                    result["index"] = str(i + start_index + 1)

        assert results[0]["index"] == "1"


class TestLogging:
    """Tests for logging functionality."""

    def test_log_message_format(self):
        """Should log filtering results."""
        final_count = 15
        original_count = 30
        reorder = True
        reindex = True

        log_message = (
            f"Cross-engine filtering kept {final_count} out of {original_count} "
            f"results with reordering={reorder}, reindex={reindex}"
        )

        assert "15" in log_message
        assert "30" in log_message
        assert "reordering=True" in log_message

    def test_log_no_reorder(self):
        """Should log when not reordering."""
        final_count = 10
        original_count = 20

        log_message = (
            f"Cross-engine filtering kept {final_count} out of {original_count} "
            f"results without reordering"
        )

        assert "without reordering" in log_message
