"""
Tests for news/core/search_integration.py

Tests cover:
- NewsSearchCallback initialization
- tracking_enabled property
- __call__() method for processing search completion
- _track_user_search() method
- _calculate_quality() method
- create_search_wrapper() function
"""

from unittest.mock import MagicMock, patch


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

    def test_init_sets_tracking_enabled_to_none(self):
        """Test that initialization sets _tracking_enabled to None."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        assert callback._tracking_enabled is None


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

    def test_tracking_enabled_default_is_false(self):
        """Test that tracking_enabled defaults to False."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # First access should initialize it to False
        assert callback.tracking_enabled is False

    def test_tracking_enabled_caches_value(self):
        """Test that tracking_enabled caches the value."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # First access
        _ = callback.tracking_enabled

        # Should be cached now
        assert callback._tracking_enabled is False

        # Second access should use cached value
        assert callback.tracking_enabled is False


class TestNewsSearchCallbackCall:
    """Tests for NewsSearchCallback.__call__() method."""

    def test_call_with_no_context(self):
        """Test __call__ with no context provided."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = False  # Disable tracking

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

    def test_call_extracts_context(self):
        """Test __call__ extracts context values correctly."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        context = {
            "is_user_search": True,
            "user_id": "user-123",
            "search_id": "search-456",
        }

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

            mock_track.assert_called_once()
            call_kwargs = mock_track.call_args[1]
            assert call_kwargs["user_id"] == "user-123"
            assert call_kwargs["search_id"] == "search-456"

    def test_call_uses_defaults_when_context_missing(self):
        """Test __call__ uses defaults when context values are missing."""
        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()
            call_kwargs = mock_track.call_args[1]
            assert call_kwargs["user_id"] == "anonymous"
            # search_id should be a generated UUID
            assert len(call_kwargs["search_id"]) == 36

    def test_call_skips_tracking_when_disabled(self):
        """Test __call__ skips tracking when 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_call_skips_tracking_for_non_user_search(self):
        """Test __call__ skips tracking for 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_call_tracks_when_enabled_and_user_search(self):
        """Test __call__ tracks when enabled and is_user_search is 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": [{"content": "result"}]},
                {"is_user_search": True, "user_id": "user-123"},
            )

            mock_track.assert_called_once()


class TestNewsSearchCallbackTrackUserSearch:
    """Tests for NewsSearchCallback._track_user_search() method."""

    def test_track_user_search_handles_exceptions_gracefully(self):
        """Test _track_user_search handles exceptions gracefully."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # The method has a try/except that catches all exceptions
        # We test that it doesn't raise even when internal imports fail
        # This is tested by verifying no exception is raised
        callback._track_user_search(
            search_id="search-123",
            user_id="user-456",
            query="test query",
            result={},
        )
        # If we get here without an exception, the test passes


class TestNewsSearchCallbackCalculateQuality:
    """Tests for NewsSearchCallback._calculate_quality() method."""

    def test_calculate_quality_empty_results_return_zero(self):
        """Test _calculate_quality returns 0 for empty results."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

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

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

    def test_calculate_quality_with_findings(self):
        """Test _calculate_quality with findings."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # 5 findings with content
        result = callback._calculate_quality(
            {"findings": [{"content": "result"}] * 5}
        )

        # count_score = min(5/10, 1.0) = 0.5
        # content_score = 1.0 (has content)
        # (0.5 + 1.0) / 2 = 0.75
        assert result == 0.75

    def test_calculate_quality_count_score_capped_at_one(self):
        """Test _calculate_quality caps count score at 1.0."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # 20 findings - should cap at 1.0
        result = callback._calculate_quality(
            {"findings": [{"content": "result"}] * 20}
        )

        # count_score = min(20/10, 1.0) = 1.0
        # content_score = 1.0
        # (1.0 + 1.0) / 2 = 1.0
        assert result == 1.0

    def test_calculate_quality_without_content(self):
        """Test _calculate_quality with findings but no content."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # 10 findings without content
        result = callback._calculate_quality({"findings": [{}] * 10})

        # count_score = min(10/10, 1.0) = 1.0
        # content_score = 0.5 (no content)
        # (1.0 + 0.5) / 2 = 0.75
        assert result == 0.75

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

        callback = NewsSearchCallback()

        # First 5 have no content, rest have content
        findings = [{} for _ in range(5)] + [{"content": "test"}] * 5

        result = callback._calculate_quality({"findings": findings})

        # count_score = min(10/10, 1.0) = 1.0
        # content_score = 0.5 (first 5 have no content)
        # (1.0 + 0.5) / 2 = 0.75
        assert result == 0.75


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

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

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

        wrapped = create_search_wrapper(original_search)

        assert callable(wrapped)

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

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

        wrapped = create_search_wrapper(my_search_method)

        assert wrapped.__name__ == "my_search_method"

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

        def my_search_method(self, query, **kwargs):
            """This is my search method docstring."""
            return {"findings": []}

        wrapped = create_search_wrapper(my_search_method)

        assert wrapped.__doc__ == "This is my search method docstring."

    def test_wrapped_search_calls_original_method(self):
        """Test wrapped search calls the original method."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        # Create a function that tracks calls
        calls = []

        def original_search(self, query, **kwargs):
            calls.append({"self": self, "query": query, "kwargs": kwargs})
            return {"findings": []}

        wrapped = create_search_wrapper(original_search)

        mock_self = MagicMock()
        result = wrapped(mock_self, "test query", extra_param="value")

        assert len(calls) == 1
        assert calls[0]["self"] is mock_self
        assert calls[0]["query"] == "test query"
        assert calls[0]["kwargs"]["extra_param"] == "value"
        assert result == {"findings": []}

    def test_wrapped_search_pops_custom_kwargs(self):
        """Test wrapped search pops custom kwargs before passing to original."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        received_kwargs = {}

        def original_search(self, query, **kwargs):
            received_kwargs.update(kwargs)
            return {"findings": []}

        wrapped = create_search_wrapper(original_search)

        mock_self = MagicMock()
        wrapped(
            mock_self,
            "test query",
            is_user_search=True,
            is_news_search=False,
            user_id="user-123",
            extra_param="value",
        )

        # Custom kwargs should be popped
        assert "is_user_search" not in received_kwargs
        assert "is_news_search" not in received_kwargs
        assert "user_id" not in received_kwargs
        assert received_kwargs["extra_param"] == "value"

    def test_wrapped_search_handles_callback_errors_gracefully(self):
        """Test wrapped search handles callback errors gracefully."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

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

        wrapped = create_search_wrapper(original_search)

        # Create a callback that raises an error
        with patch(
            "local_deep_research.news.core.search_integration.NewsSearchCallback"
        ) as MockCallback:
            mock_instance = MagicMock()
            mock_instance.side_effect = Exception("Callback error")
            MockCallback.return_value = mock_instance

            # Create a fresh wrapped function with the patched callback
            wrapped = create_search_wrapper(original_search)

            mock_self = MagicMock()
            # Should not raise, should still return result
            result = wrapped(mock_self, "test query")

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