"""
Extended tests for news/core/search_integration.py

Tests cover:
- NewsSearchCallback initialization
- NewsSearchCallback.tracking_enabled property
- NewsSearchCallback.__call__() method
- NewsSearchCallback._track_user_search() method
- NewsSearchCallback._calculate_quality() method
- create_search_wrapper() function
- Wrapped search behavior
"""

from unittest.mock import Mock, patch


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

    def test_init_sets_tracking_enabled_none(self):
        """Init sets _tracking_enabled to None."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        assert callback._tracking_enabled is None

    def test_tracking_enabled_returns_false_by_default(self):
        """tracking_enabled returns False by default."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        assert callback.tracking_enabled is False

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

        callback = NewsSearchCallback()
        # Access twice
        _ = callback.tracking_enabled
        result = callback.tracking_enabled

        # Value should be cached
        assert callback._tracking_enabled is False
        assert result is False


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

    def test_call_extracts_user_id_from_context(self):
        """__call__ extracts 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": []},
                context={"user_id": "user123", "is_user_search": True},
            )

            mock_track.assert_called_once()
            call_args = mock_track.call_args
            assert call_args[1]["user_id"] == "user123"

    def test_call_uses_anonymous_for_missing_user_id(self):
        """__call__ uses 'anonymous' for missing user_id."""
        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": []}, context={"is_user_search": True}
            )

            mock_track.assert_called_once()
            call_args = mock_track.call_args
            assert call_args[1]["user_id"] == "anonymous"

    def test_call_does_not_track_system_searches(self):
        """__call__ 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": []},
                context={"is_user_search": False},
            )

            mock_track.assert_not_called()

    def test_call_generates_search_id_if_missing(self):
        """__call__ 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": []}, context={"is_user_search": True}
            )

            mock_track.assert_called_once()
            call_args = mock_track.call_args
            # Should have a UUID-like search_id
            assert (
                len(call_args[1]["search_id"]) == 36
            )  # UUID length with dashes


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

    def test_empty_findings_returns_zero(self):
        """Empty findings returns 0.0."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

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

        assert result == 0.0

    def test_no_findings_key_returns_zero(self):
        """Missing findings key returns 0.0."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

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

        assert result == 0.0

    def test_one_finding_with_content_scores_high(self):
        """One finding with content scores between 0.5 and 1.0."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality(
            {"findings": [{"content": "Some content"}]}
        )

        assert 0.5 <= result <= 1.0

    def test_ten_findings_scores_high(self):
        """Ten findings scores close to max."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()
        result = callback._calculate_quality(
            {"findings": [{"content": f"Content {i}"} for i in range(10)]}
        )

        assert result >= 0.9

    def test_findings_without_content_scores_lower(self):
        """Findings without content score lower."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        # With content
        with_content = callback._calculate_quality(
            {"findings": [{"content": "Content"}]}
        )

        # Without content
        without_content = callback._calculate_quality({"findings": [{}]})

        assert without_content < with_content

    def test_more_findings_scores_higher(self):
        """More findings scores higher than fewer."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )

        callback = NewsSearchCallback()

        few_findings = callback._calculate_quality(
            {"findings": [{} for _ in range(2)]}
        )

        many_findings = callback._calculate_quality(
            {"findings": [{} for _ in range(8)]}
        )

        assert many_findings > few_findings


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

    def _create_named_mock(self, return_value):
        """Create a mock with __name__ attribute."""
        mock = Mock(return_value=return_value)
        mock.__name__ = "test_search_method"
        return mock

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

        original = self._create_named_mock({"findings": []})
        wrapped = create_search_wrapper(original)

        assert callable(wrapped)

    def test_wrapper_calls_original_method(self):
        """Wrapper calls original search method."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        original = self._create_named_mock({"findings": []})
        wrapped = create_search_wrapper(original)

        mock_self = Mock()
        wrapped(mock_self, "test query")

        original.assert_called_once()

    def test_wrapper_passes_query_to_original(self):
        """Wrapper passes query to original method."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        original = self._create_named_mock({"findings": []})
        wrapped = create_search_wrapper(original)

        mock_self = Mock()
        wrapped(mock_self, "my search query")

        call_args = original.call_args
        assert call_args[0][1] == "my search query"

    def test_wrapper_returns_original_result(self):
        """Wrapper returns result from original method."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        expected_result = {"findings": [{"content": "result"}], "status": "ok"}
        original = self._create_named_mock(expected_result)
        wrapped = create_search_wrapper(original)

        mock_self = Mock()
        result = wrapped(mock_self, "query")

        assert result == expected_result

    def test_wrapper_strips_tracking_kwargs(self):
        """Wrapper strips is_user_search, is_news_search, user_id from kwargs."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        original = self._create_named_mock({"findings": []})
        wrapped = create_search_wrapper(original)

        mock_self = Mock()
        wrapped(
            mock_self,
            "query",
            is_user_search=True,
            is_news_search=False,
            user_id="user1",
            other_param="value",
        )

        # Original should receive other_param but not tracking kwargs
        call_kwargs = original.call_args[1]
        assert "is_user_search" not in call_kwargs
        assert "is_news_search" not in call_kwargs
        assert "user_id" not in call_kwargs
        assert call_kwargs.get("other_param") == "value"

    def test_wrapper_handles_callback_error(self):
        """Wrapper handles callback error gracefully."""
        from local_deep_research.news.core.search_integration import (
            create_search_wrapper,
        )

        original = self._create_named_mock({"findings": []})
        wrapped = create_search_wrapper(original)

        # The callback is internal, but the wrapper should not raise
        # even if callback has issues
        mock_self = Mock()
        result = wrapped(mock_self, "query")

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


class TestWrappedSearchContext:
    """Tests for wrapped search context building."""

    def _create_named_mock(self, return_value):
        """Create a mock with __name__ attribute."""
        mock = Mock(return_value=return_value)
        mock.__name__ = "test_search_method"
        return mock


class TestNewsSearchCallbackDefaults:
    """Tests for NewsSearchCallback default behavior."""

    def test_is_user_search_defaults_to_true(self):
        """is_user_search defaults to True 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:
            # Pass context without is_user_search
            callback("query", {"findings": []}, context={})

            # Should track because is_user_search defaults to True
            mock_track.assert_called_once()

    def test_search_id_is_uuid_format(self):
        """Generated search_id is UUID format."""
        from local_deep_research.news.core.search_integration import (
            NewsSearchCallback,
        )
        import uuid

        callback = NewsSearchCallback()
        callback._tracking_enabled = True

        captured_id = None

        def capture_track(search_id, user_id, query, result):
            nonlocal captured_id
            captured_id = search_id

        with patch.object(callback, "_track_user_search", capture_track):
            callback(
                "query", {"findings": []}, context={"is_user_search": True}
            )

        # Should be valid UUID
        assert captured_id is not None
        uuid.UUID(captured_id)  # Raises if invalid
