"""Unit tests for src/timmy/research_tools.py.

Refs #1237
"""

from __future__ import annotations

import sys
from types import ModuleType
from unittest.mock import MagicMock, patch

import pytest

pytestmark = pytest.mark.unit

# ── Stub serpapi before any import of research_tools ─────────────────────────

_serpapi_stub = ModuleType("serpapi")
_google_search_mock = MagicMock()
_serpapi_stub.GoogleSearch = _google_search_mock
sys.modules.setdefault("serpapi", _serpapi_stub)


# ── google_web_search ─────────────────────────────────────────────────────────


class TestGoogleWebSearch:
    """google_web_search returns results or degrades gracefully."""

    @pytest.mark.asyncio
    async def test_returns_empty_string_when_no_api_key(self, monkeypatch):
        monkeypatch.delenv("SERPAPI_API_KEY", raising=False)
        from timmy.research_tools import google_web_search

        result = await google_web_search("test query")
        assert result == ""

    @pytest.mark.asyncio
    async def test_logs_warning_when_no_api_key(self, monkeypatch, caplog):
        import logging

        monkeypatch.delenv("SERPAPI_API_KEY", raising=False)
        from timmy.research_tools import google_web_search

        with caplog.at_level(logging.WARNING, logger="timmy.research_tools"):
            await google_web_search("test query")

        assert any("SERPAPI_API_KEY" in rec.message for rec in caplog.records)

    @pytest.mark.asyncio
    async def test_calls_google_search_with_api_key(self, monkeypatch):
        monkeypatch.setenv("SERPAPI_API_KEY", "fake-key-123")

        mock_instance = MagicMock()
        mock_instance.get_dict.return_value = {"organic_results": [{"title": "Result"}]}

        with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance) as mock_cls:
            from timmy.research_tools import google_web_search

            result = await google_web_search("hello world")

        mock_cls.assert_called_once()
        call_params = mock_cls.call_args[0][0]
        assert call_params["q"] == "hello world"
        assert call_params["api_key"] == "fake-key-123"
        mock_instance.get_dict.assert_called_once()
        assert "organic_results" in result

    @pytest.mark.asyncio
    async def test_returns_string_result(self, monkeypatch):
        monkeypatch.setenv("SERPAPI_API_KEY", "key")

        mock_instance = MagicMock()
        mock_instance.get_dict.return_value = {"answer": 42}

        with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance):
            from timmy.research_tools import google_web_search

            result = await google_web_search("query")

        assert isinstance(result, str)

    @pytest.mark.asyncio
    async def test_passes_query_to_params(self, monkeypatch):
        monkeypatch.setenv("SERPAPI_API_KEY", "k")

        mock_instance = MagicMock()
        mock_instance.get_dict.return_value = {}

        with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance) as mock_cls:
            from timmy.research_tools import google_web_search

            await google_web_search("specific search term")

        params = mock_cls.call_args[0][0]
        assert params["q"] == "specific search term"


# ── get_llm_client ────────────────────────────────────────────────────────────


class TestGetLLMClient:
    """get_llm_client returns a client with a completion method."""

    def test_returns_non_none_client(self):
        from timmy.research_tools import get_llm_client

        client = get_llm_client()
        assert client is not None

    def test_client_has_completion_method(self):
        from timmy.research_tools import get_llm_client

        client = get_llm_client()
        assert hasattr(client, "completion")
        assert callable(client.completion)

    @pytest.mark.asyncio
    async def test_completion_returns_object_with_text(self):
        from timmy.research_tools import get_llm_client

        client = get_llm_client()
        result = await client.completion("test prompt", max_tokens=100)
        assert hasattr(result, "text")

    @pytest.mark.asyncio
    async def test_completion_text_is_string(self):
        from timmy.research_tools import get_llm_client

        client = get_llm_client()
        result = await client.completion("any prompt", max_tokens=50)
        assert isinstance(result.text, str)

    @pytest.mark.asyncio
    async def test_completion_text_contains_prompt(self):
        from timmy.research_tools import get_llm_client

        client = get_llm_client()
        result = await client.completion("my prompt", max_tokens=50)
        assert "my prompt" in result.text

    def test_each_call_returns_new_client(self):
        from timmy.research_tools import get_llm_client

        client_a = get_llm_client()
        client_b = get_llm_client()
        # Both should be functional clients (not necessarily the same instance)
        assert hasattr(client_a, "completion")
        assert hasattr(client_b, "completion")
