"""Unit tests for WebSearchTool — multi-backend web search."""

import pytest
from unittest.mock import AsyncMock, patch, MagicMock

from agentkit.tools.web_search import WebSearchTool


class TestWebSearchToolSchema:
    """Test schema definitions."""

    def test_input_schema_has_required_fields(self):
        tool = WebSearchTool()
        schema = tool.input_schema
        assert "query" in schema["properties"]
        assert "query" in schema["required"]
        assert "max_results" in schema["properties"]

    def test_output_schema_has_required_fields(self):
        tool = WebSearchTool()
        schema = tool.output_schema
        assert "results" in schema["properties"]
        assert "total" in schema["properties"]
        "backend" in schema["properties"]
        assert "success" in schema["properties"]


class TestWebSearchToolValidation:
    """Test input validation."""

    @pytest.mark.asyncio
    async def test_missing_query(self):
        tool = WebSearchTool()
        result = await tool.execute()
        assert result["success"] is False
        assert "query" in result["error"]

    @pytest.mark.asyncio
    async def test_empty_query(self):
        tool = WebSearchTool()
        result = await tool.execute(query="")
        assert result["success"] is False


class TestWebSearchToolDuckDuckGo:
    """Test DuckDuckGo fallback parsing."""

    def test_parse_html_with_results(self):
        html = """
        <html><body>
        <a class="result-link" href="https://example.com/result1">Result 1 Title</a>
        <td class="result-snippet">Snippet for result 1</td>
        <a class="result-link" href="https://example.com/result2">Result 2 Title</a>
        <td class="result-snippet">Snippet for result 2</td>
        </body></html>
        """
        results = WebSearchTool._parse_duckduckgo_html(html, 5)
        assert len(results) == 2
        assert results[0]["title"] == "Result 1 Title"
        assert results[0]["url"] == "https://example.com/result1"
        assert results[0]["snippet"] == "Snippet for result 1"

    def test_parse_html_empty(self):
        results = WebSearchTool._parse_duckduckgo_html("<html></html>", 5)
        assert results == []

    def test_parse_html_skips_duckduckgo_links(self):
        html = """
        <a class="result-link" href="https://duckduckgo.com/internal">Internal</a>
        <a class="result-link" href="https://example.com/good">Good Result</a>
        <td class="result-snippet">Good snippet</td>
        """
        results = WebSearchTool._parse_duckduckgo_html(html, 5)
        assert len(results) == 1
        assert results[0]["url"] == "https://example.com/good"

    def test_parse_html_max_results(self):
        html = ""
        for i in range(10):
            html += f'<a class="result-link" href="https://example.com/{i}">Title {i}</a>\n'
            html += f'<td class="result-snippet">Snippet {i}</td>\n'
        results = WebSearchTool._parse_duckduckgo_html(html, 3)
        assert len(results) == 3


class TestWebSearchToolTavily:
    """Test Tavily API backend."""

    @pytest.mark.asyncio
    async def test_tavily_success(self):
        tool = WebSearchTool(tavily_api_key="test-key")

        mock_response = MagicMock()
        mock_response.json.return_value = {
            "results": [
                {"title": "Test", "url": "https://example.com", "content": "Test content"},
            ]
        }
        mock_response.raise_for_status = MagicMock()

        with patch("httpx.AsyncClient") as mock_client_cls:
            mock_client = AsyncMock()
            mock_client.post = AsyncMock(return_value=mock_response)
            mock_client.__aenter__ = AsyncMock(return_value=mock_client)
            mock_client.__aexit__ = AsyncMock(return_value=None)
            mock_client_cls.return_value = mock_client

            result = await tool.execute(query="test query")
            assert result["success"] is True
            assert result["backend"] == "tavily"
            assert len(result["results"]) == 1

    @pytest.mark.asyncio
    async def test_tavily_failure_falls_back(self):
        tool = WebSearchTool(tavily_api_key="test-key")

        with patch.object(tool, "_search_tavily", return_value={"success": False, "error": "API error", "results": [], "total": 0}):
            with patch.object(tool, "_search_duckduckgo", return_value={"results": [], "total": 0, "backend": "duckduckgo", "success": True}):
                result = await tool.execute(query="test")
                assert result["backend"] == "duckduckgo"


class TestWebSearchToolSerper:
    """Test Serper API backend."""

    @pytest.mark.asyncio
    async def test_serper_success(self):
        tool = WebSearchTool(serper_api_key="test-key")

        mock_response = MagicMock()
        mock_response.json.return_value = {
            "organic": [
                {"title": "Test", "link": "https://example.com", "snippet": "Test snippet"},
            ]
        }
        mock_response.raise_for_status = MagicMock()

        with patch("httpx.AsyncClient") as mock_client_cls:
            mock_client = AsyncMock()
            mock_client.post = AsyncMock(return_value=mock_response)
            mock_client.__aenter__ = AsyncMock(return_value=mock_client)
            mock_client.__aexit__ = AsyncMock(return_value=None)
            mock_client_cls.return_value = mock_client

            result = await tool.execute(query="test query")
            assert result["success"] is True
            assert result["backend"] == "serper"
            assert len(result["results"]) == 1


class TestWebSearchToolPriority:
    """Test backend priority and fallback chain."""

    @pytest.mark.asyncio
    async def test_tavily_over_serper(self):
        """Tavily should be tried before Serper when both keys are available."""
        tool = WebSearchTool(tavily_api_key="t-key", serper_api_key="s-key")

        with patch.object(tool, "_search_tavily", return_value={"results": [], "total": 0, "backend": "tavily", "success": True}) as mock_tavily:
            result = await tool.execute(query="test")
            mock_tavily.assert_called_once()
            assert result["backend"] == "tavily"

    @pytest.mark.asyncio
    async def test_no_keys_uses_duckduckgo(self):
        """Without API keys, DuckDuckGo is used directly."""
        tool = WebSearchTool()

        with patch.object(tool, "_search_duckduckgo", return_value={"results": [], "total": 0, "backend": "duckduckgo", "success": True}) as mock_ddg:
            result = await tool.execute(query="test")
            mock_ddg.assert_called_once()
            assert result["backend"] == "duckduckgo"
