from unittest.mock import Mock, patch

import pytest


class TestTinyFishSearchEngineInit:
    """Tests for TinyFishSearchEngine initialization."""

    def test_init_with_api_key(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        engine = TinyFishSearchEngine(api_key="test-api-key")

        assert engine.api_key == "test-api-key"
        assert engine.max_results == 10
        assert engine.location == "US"
        assert engine.language == "en"
        assert engine.fetch_format == "markdown"
        assert engine.include_full_content is True
        assert engine.search_snippets_only is False

    def test_init_without_api_key_raises(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        with pytest.raises(
            ValueError, match="No valid API key found for TinyFish"
        ):
            TinyFishSearchEngine(settings_snapshot={})

    def test_init_with_api_key_from_settings(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        engine = TinyFishSearchEngine(
            settings_snapshot={
                "search.engine.web.tinyfish.api_key": "settings-key"
            }
        )

        assert engine.api_key == "settings-key"

    def test_init_with_snippet_only_override(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        engine = TinyFishSearchEngine(
            api_key="test-key",
            include_full_content=True,
            search_snippets_only=True,
        )

        assert engine.include_full_content is True
        assert engine.search_snippets_only is True

    def test_init_normalizes_location_and_common_language_name(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        engine = TinyFishSearchEngine(
            api_key="test-key",
            location="us",
            language="English",
        )

        assert engine.location == "US"
        assert engine.language == "en"


class TestTinyFishGetPreviews:
    """Tests for TinyFish preview search."""

    def test_get_previews_returns_formatted_results(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "position": 1,
                    "site_name": "example.com",
                    "title": "Result 1",
                    "snippet": "Snippet 1",
                    "url": "https://example.com/page",
                },
                {
                    "position": 2,
                    "title": "Result 2",
                    "snippet": "Snippet 2",
                    "url": "https://docs.example.org/post",
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = TinyFishSearchEngine(api_key="test-key", max_results=5)
            previews = engine._get_previews("test query")

        assert len(previews) == 2
        assert previews[0]["id"] == "https://example.com/page"
        assert previews[0]["title"] == "Result 1"
        assert previews[0]["link"] == "https://example.com/page"
        assert previews[0]["snippet"] == "Snippet 1"
        assert previews[0]["displayed_link"] == "example.com"
        assert previews[1]["displayed_link"] == "docs.example.org"

        call_kwargs = mock_get.call_args.kwargs
        assert call_kwargs["params"] == {
            "query": "test query",
            "location": "US",
            "language": "en",
        }
        assert call_kwargs["headers"] == {"X-API-Key": "test-key"}
        assert call_kwargs["timeout"] == 10

    def test_get_previews_limits_query_and_results(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "title": f"Result {idx}",
                    "url": f"https://example.com/{idx}",
                    "snippet": "Snippet",
                }
                for idx in range(5)
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
            return_value=mock_response,
        ) as mock_get:
            engine = TinyFishSearchEngine(api_key="test-key", max_results=2)
            previews = engine._get_previews("x" * 500)

        assert len(previews) == 2
        assert (
            len(mock_get.call_args.kwargs["params"]["query"])
            == TinyFishSearchEngine.MAX_QUERY_LEN
        )

    def test_get_previews_rate_limit_error(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        mock_response = Mock()
        mock_response.status_code = 429
        mock_response.text = "Rate limit exceeded"

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
            return_value=mock_response,
        ):
            engine = TinyFishSearchEngine(api_key="test-key")

            with pytest.raises(RateLimitError):
                engine._get_previews("test query")

    def test_get_previews_request_exception(self):
        import requests

        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
            side_effect=requests.exceptions.RequestException(
                "Connection error"
            ),
        ):
            engine = TinyFishSearchEngine(api_key="test-key")

            assert engine._get_previews("test query") == []

    def test_get_previews_request_exception_does_not_log_query(self):
        """On a request error the user's query must not reach the logs.

        The query rides in the request URL (params=query=...), so a requests
        exception string embeds it; the old logger.exception leaked it. We now
        log a static message + status only, and never logger.exception here.
        """
        import requests

        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        secret_query = "super-secret-user-query"
        error = requests.exceptions.RequestException(
            "404 Client Error for url: "
            f"https://api.search.tinyfish.ai/?query={secret_query}"
        )

        with (
            patch(
                "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
                side_effect=error,
            ),
            patch(
                "local_deep_research.web_search_engines.engines.search_engine_tinyfish.logger"
            ) as mock_logger,
        ):
            engine = TinyFishSearchEngine(api_key="test-key")
            assert engine._get_previews(secret_query) == []

        logged = " ".join(
            str(arg) for call in mock_logger.mock_calls for arg in call.args
        )
        assert secret_query not in logged
        mock_logger.exception.assert_not_called()
        assert mock_logger.warning.call_count >= 1

    def test_get_previews_request_exception_with_rate_limit_response(self):
        import requests

        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        response = Mock()
        response.status_code = 429
        response.text = "Rate limit exceeded"
        error = requests.exceptions.RequestException("rate limited")
        error.response = response

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_get",
            side_effect=error,
        ):
            engine = TinyFishSearchEngine(api_key="test-key")

            with pytest.raises(RateLimitError):
                engine._get_previews("test query")


class TestTinyFishFullContent:
    """Tests for TinyFish Fetch enrichment."""

    def test_get_full_content_enriches_results(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "url": "https://example.com/page",
                    "final_url": "https://example.com/page",
                    "title": "Fetched title",
                    "description": "Fetched description",
                    "language": "en",
                    "text": "# Extracted content",
                }
            ],
            "errors": [],
        }

        relevant_items = [
            {
                "title": "Search title",
                "link": "https://example.com/page",
                "snippet": "Search snippet",
            }
        ]

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = TinyFishSearchEngine(api_key="test-key")
            results = engine._get_full_content(relevant_items)

        assert results[0]["title"] == "Search title"
        assert results[0]["content"] == "# Extracted content"
        assert results[0]["description"] == "Fetched description"
        assert results[0]["language"] == "en"

        call_kwargs = mock_post.call_args.kwargs
        assert call_kwargs["json"] == {
            "urls": ["https://example.com/page"],
            "format": "markdown",
        }
        assert call_kwargs["headers"] == {
            "X-API-Key": "test-key",
            "Content-Type": "application/json",
        }
        assert call_kwargs["timeout"] == 150

    def test_get_full_content_skips_fetch_when_disabled(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_post"
        ) as mock_post:
            engine = TinyFishSearchEngine(
                api_key="test-key", include_full_content=False
            )
            results = engine._get_full_content(
                [{"title": "A", "link": "https://example.com"}]
            )

        assert results == [{"title": "A", "link": "https://example.com"}]
        mock_post.assert_not_called()

    def test_get_full_content_limits_batch_to_ten_urls(self):
        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"results": [], "errors": []}

        relevant_items = [
            {"title": str(idx), "link": f"https://example.com/{idx}"}
            for idx in range(12)
        ]

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = TinyFishSearchEngine(api_key="test-key")
            engine._get_full_content(relevant_items)

        assert (
            len(mock_post.call_args.kwargs["json"]["urls"])
            == TinyFishSearchEngine.MAX_FETCH_URLS
        )

    def test_get_full_content_request_exception_with_rate_limit_response(self):
        import requests

        from local_deep_research.web_search_engines.engines.search_engine_tinyfish import (
            TinyFishSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        response = Mock()
        response.status_code = 429
        response.text = "Rate limit exceeded"
        error = requests.exceptions.RequestException("rate limited")
        error.response = response

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_tinyfish.safe_post",
            side_effect=error,
        ):
            engine = TinyFishSearchEngine(api_key="test-key")

            with pytest.raises(RateLimitError):
                engine._get_full_content(
                    [{"title": "A", "link": "https://example.com"}]
                )
