"""
Comprehensive tests for search integration module.
Tests NewsSearchCallback, create_search_wrapper, and related functions.
"""

import pytest
from unittest.mock import Mock, patch
import uuid


class TestNewsSearchCallbackInit:
    """Tests for NewsSearchCallback initialization."""

    def test_initializes_tracking_enabled_to_none(self):
        """Test _tracking_enabled is None on init."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        assert callback._tracking_enabled is None

    def test_creates_instance(self):
        """Test can create instance."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        assert callback is not None


class TestNewsSearchCallbackTrackingEnabled:
    """Tests for tracking_enabled property."""

    def test_returns_false_by_default(self):
        """Test tracking is disabled by default."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback.tracking_enabled
        assert result is False

    def test_caches_value_after_first_call(self):
        """Test value is cached after first access."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        _ = callback.tracking_enabled
        assert callback._tracking_enabled is not None

    def test_returns_cached_value_on_subsequent_calls(self):
        """Test returns cached value on subsequent calls."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result1 = callback.tracking_enabled
        result2 = callback.tracking_enabled
        assert result1 == result2


class TestNewsSearchCallbackCall:
    """Tests for __call__ method."""

    def test_callable(self):
        """Test instance is callable."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        assert callable(callback)

    def test_accepts_query_and_result(self):
        """Test accepts query and result arguments."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        # Should not raise
        callback("test query", {"findings": []})

    def test_accepts_optional_context(self):
        """Test accepts optional context argument."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback("test query", {"findings": []}, {"user_id": "test"})

    def test_handles_none_context(self):
        """Test handles None context."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback("test query", {"findings": []}, None)

    def test_does_not_track_when_disabled(self):
        """Test does not track when tracking is disabled."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = False

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {"is_user_search": True})
            mock_track.assert_not_called()

    def test_does_not_track_non_user_searches(self):
        """Test does not track non-user searches."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {"is_user_search": False})
            mock_track.assert_not_called()

    def test_tracks_when_enabled_and_user_search(self):
        """Test tracks when enabled and is_user_search."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {"is_user_search": True})
            mock_track.assert_called_once()

    def test_defaults_is_user_search_to_true(self):
        """Test defaults is_user_search to True."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {})
            mock_track.assert_called_once()

    def test_defaults_user_id_to_anonymous(self):
        """Test defaults user_id to 'anonymous'."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {"is_user_search": True})
            call_args = mock_track.call_args
            assert call_args[1]["user_id"] == "anonymous"

    def test_uses_provided_user_id(self):
        """Test uses provided user_id from context."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback(
                "test query",
                {"findings": []},
                {"is_user_search": True, "user_id": "user123"},
            )
            call_args = mock_track.call_args
            assert call_args[1]["user_id"] == "user123"

    def test_generates_search_id_if_not_provided(self):
        """Test generates search_id if not in context."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback("test query", {"findings": []}, {"is_user_search": True})
            call_args = mock_track.call_args
            # Should be a valid UUID
            search_id = call_args[1]["search_id"]
            uuid.UUID(search_id)

    def test_uses_provided_search_id(self):
        """Test uses provided search_id from context."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        with patch.object(callback, "_track_user_search") as mock_track:
            callback(
                "test query",
                {"findings": []},
                {"is_user_search": True, "search_id": "custom-id"},
            )
            call_args = mock_track.call_args
            assert call_args[1]["search_id"] == "custom-id"


class TestTrackUserSearch:
    """Tests for _track_user_search method."""

    def test_handles_import_error_gracefully(self):
        """Test handles import error when SearchTracker unavailable."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        # Should not raise even if SearchTracker is not available
        callback._track_user_search(
            search_id="123",
            user_id="user1",
            query="test query",
            result={"findings": []},
        )
        # If it doesn't raise, the test passes

    def test_track_user_search_method_exists(self):
        """Test _track_user_search method exists."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        assert hasattr(callback, "_track_user_search")
        assert callable(callback._track_user_search)

    def test_track_user_search_accepts_parameters(self):
        """Test _track_user_search accepts required parameters."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        # Should accept these parameters without error
        callback._track_user_search(
            search_id="search-123",
            user_id="user-456",
            query="test query",
            result={"findings": [{"content": "test"}], "strategy": "deep"},
        )


class TestCalculateQuality:
    """Tests for _calculate_quality method."""

    def test_returns_float(self):
        """Test returns a float."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality({"findings": []})
        assert isinstance(result, float)

    def test_returns_zero_for_no_findings(self):
        """Test returns 0 when no findings."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality({"findings": []})
        assert result == 0.0

    def test_returns_zero_for_missing_findings(self):
        """Test returns 0 when findings key is missing."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality({})
        assert result == 0.0

    def test_returns_higher_for_more_findings(self):
        """Test returns higher score for more findings."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        few_findings = {"findings": [{}] * 3}
        many_findings = {"findings": [{}] * 10}

        score_few = callback._calculate_quality(few_findings)
        score_many = callback._calculate_quality(many_findings)

        assert score_many > score_few

    def test_caps_count_score_at_one(self):
        """Test count score is capped at 1."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # 20 findings should have count_score of 1.0
        many_findings = {"findings": [{}] * 20}
        result = callback._calculate_quality(many_findings)

        # (1.0 + 0.5) / 2 = 0.75 (no content)
        assert result <= 1.0

    def test_higher_with_content(self):
        """Test returns higher score when findings have content."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        no_content = {"findings": [{}] * 5}
        with_content = {"findings": [{"content": "text"}] * 5}

        score_no_content = callback._calculate_quality(no_content)
        score_with_content = callback._calculate_quality(with_content)

        assert score_with_content > score_no_content

    def test_checks_first_five_findings_for_content(self):
        """Test only checks first 5 findings for content."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # Content in 6th finding shouldn't affect score
        findings = [{}] * 5 + [{"content": "text"}] * 5
        result1 = callback._calculate_quality({"findings": findings})

        # Content in first 5
        findings2 = [{"content": "text"}] * 5 + [{}] * 5
        result2 = callback._calculate_quality({"findings": findings2})

        # result2 should be higher (has content in first 5)
        assert result2 > result1


class TestCreateSearchWrapper:
    """Tests for create_search_wrapper function."""

    def test_function_exists(self):
        """Test create_search_wrapper function exists."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        assert create_search_wrapper is not None
        assert callable(create_search_wrapper)

    def test_returns_callable(self):
        """Test returns a callable."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        def original(self, query, **kwargs):
            return {}

        wrapped = create_search_wrapper(original)
        assert callable(wrapped)

    def test_preserves_method_name(self):
        """Test preserves original method name."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        def original_search(self, query, **kwargs):
            """Original docstring."""
            return {}

        wrapped = create_search_wrapper(original_search)
        assert wrapped.__name__ == "original_search"

    def test_preserves_docstring(self):
        """Test preserves original docstring."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        def original_search(self, query, **kwargs):
            """Original docstring."""
            return {}

        wrapped = create_search_wrapper(original_search)
        assert wrapped.__doc__ == """Original docstring."""

    def test_wrapped_function_calls_original(self):
        """Test wrapped function calls original."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        call_count = [0]

        def original(self, query, **kwargs):
            call_count[0] += 1
            return {"findings": []}

        wrapped = create_search_wrapper(original)
        wrapped(Mock(), "test query")

        assert call_count[0] == 1

    def test_wrapped_function_returns_result(self):
        """Test wrapped function returns original result."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        def original(self, query, **kwargs):
            return {"findings": [{"content": "test"}]}

        wrapped = create_search_wrapper(original)
        result = wrapped(Mock(), "query")

        assert result == {"findings": [{"content": "test"}]}

    def test_strips_special_kwargs(self):
        """Test strips special kwargs before calling original."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        received_kwargs = [None]

        def original(self, query, **kwargs):
            received_kwargs[0] = kwargs
            return {}

        wrapped = create_search_wrapper(original)
        wrapped(
            Mock(),
            "query",
            is_user_search=True,
            is_news_search=True,
            user_id="user123",
            extra_param="value",
        )

        # Special kwargs should be stripped
        assert "is_user_search" not in received_kwargs[0]
        assert "is_news_search" not in received_kwargs[0]
        assert "user_id" not in received_kwargs[0]
        # Regular kwargs should pass through
        assert received_kwargs[0].get("extra_param") == "value"


class TestSearchIntegrationEdgeCases:
    """Edge case tests for search integration."""

    def test_callback_with_empty_result(self):
        """Test callback handles empty result."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        # Should not raise
        callback("query", {})

    def test_callback_with_none_values(self):
        """Test callback handles None values in context."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback(
            "query", {"findings": []}, {"user_id": None, "search_id": None}
        )

    def test_calculate_quality_with_none_content(self):
        """Test calculate_quality with None content values."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality(
            {"findings": [{"content": None}, {"content": "text"}]}
        )
        assert result > 0

    def test_wrapper_with_method_that_raises(self):
        """Test wrapper handles original method that raises."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        def original_that_raises(self, query, **kwargs):
            raise ValueError("Search failed")

        wrapped = create_search_wrapper(original_that_raises)

        self_obj = Mock()
        with pytest.raises(ValueError, match="Search failed"):
            wrapped(self_obj, "query")

    def test_callback_tracking_enabled_is_boolean(self):
        """Test tracking_enabled returns boolean."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback.tracking_enabled
        assert isinstance(result, bool)


class TestSearchIntegrationImports:
    """Tests for module imports."""

    def test_imports_news_search_callback(self):
        """Test NewsSearchCallback can be imported."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        assert NewsSearchCallback is not None

    def test_imports_create_search_wrapper(self):
        """Test create_search_wrapper can be imported."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        assert create_search_wrapper is not None
        assert callable(create_search_wrapper)
