"""Tests for web search integration."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest


# ── Backend detection ──────────────────────────────────────────────────────


class TestGetBackend:
    """Test backend auto-detection based on config."""

    @patch("alive.integrations.web_search._load_config", return_value={})
    def test_default_backend_is_duckduckgo(self, mock_cfg):
        from alive.integrations.web_search import _get_backend

        assert _get_backend() == "duckduckgo"

    @patch("alive.integrations.web_search._load_config", return_value={"tavily_key": "tvly-xxx"})
    def test_tavily_when_key_set(self, mock_cfg):
        from alive.integrations.web_search import _get_backend

        assert _get_backend() == "tavily"

    @patch("alive.integrations.web_search._load_config", return_value={"searxng_url": "http://localhost:8888"})
    def test_searxng_when_url_set(self, mock_cfg):
        from alive.integrations.web_search import _get_backend

        assert _get_backend() == "searxng"

    @patch("alive.integrations.web_search._load_config", return_value={"tavily_key": "tvly-xxx", "searxng_url": "http://localhost:8888"})
    def test_tavily_takes_priority_over_searxng(self, mock_cfg):
        from alive.integrations.web_search import _get_backend

        assert _get_backend() == "tavily"


# ── DuckDuckGo backend ────────────────────────────────────────────────────


class TestDDGBackend:
    """Test DuckDuckGo search functions."""

    def test_search_ddg_returns_list(self):
        from alive.integrations.web_search import _search_ddg

        mock_results = [
            {"title": "Python", "href": "https://python.org", "body": "Programming language"},
            {"title": "PyPI", "href": "https://pypi.org", "body": "Package index"},
        ]

        mock_ddgs = MagicMock()
        mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
        mock_ddgs.__exit__ = MagicMock(return_value=False)
        mock_ddgs.text.return_value = mock_results

        with patch("duckduckgo_search.DDGS", return_value=mock_ddgs):
            results = _search_ddg("python", max_results=2)

        assert len(results) == 2
        assert results[0]["title"] == "Python"
        assert results[0]["url"] == "https://python.org"
        assert results[0]["snippet"] == "Programming language"

    def test_news_ddg_returns_list(self):
        from alive.integrations.web_search import _news_ddg

        mock_results = [
            {"title": "AI News", "url": "https://news.com/ai", "body": "Latest AI", "date": "2026-03-18"},
        ]

        mock_ddgs = MagicMock()
        mock_ddgs.__enter__ = MagicMock(return_value=mock_ddgs)
        mock_ddgs.__exit__ = MagicMock(return_value=False)
        mock_ddgs.news.return_value = mock_results

        with patch("duckduckgo_search.DDGS", return_value=mock_ddgs):
            results = _news_ddg("AI", max_results=1)

        assert len(results) == 1
        assert results[0]["date"] == "2026-03-18"


# ── Tavily backend ────────────────────────────────────────────────────────


class TestTavilyBackend:
    """Test Tavily API search."""

    @patch("alive.integrations.web_search._load_config", return_value={"tavily_key": "tvly-test"})
    def test_search_tavily(self, mock_cfg):
        from alive.integrations.web_search import _search_tavily

        mock_resp = MagicMock()
        mock_resp.json.return_value = {
            "results": [
                {"title": "Result 1", "url": "https://example.com", "content": "Snippet 1"},
            ]
        }
        mock_resp.raise_for_status = MagicMock()

        with patch("httpx.post", return_value=mock_resp):
            results = _search_tavily("test query", max_results=1)

        assert len(results) == 1
        assert results[0]["title"] == "Result 1"
        assert results[0]["snippet"] == "Snippet 1"


# ── SearXNG backend ───────────────────────────────────────────────────────


class TestSearXNGBackend:
    """Test SearXNG search."""

    @patch("alive.integrations.web_search._load_config", return_value={"searxng_url": "http://localhost:8888"})
    def test_search_searxng(self, mock_cfg):
        from alive.integrations.web_search import _search_searxng

        mock_resp = MagicMock()
        mock_resp.json.return_value = {
            "results": [
                {"title": "SearXNG Result", "url": "https://example.com", "content": "Found it"},
            ]
        }
        mock_resp.raise_for_status = MagicMock()

        with patch("httpx.get", return_value=mock_resp):
            results = _search_searxng("test query", max_results=1)

        assert len(results) == 1
        assert results[0]["title"] == "SearXNG Result"


# ── Public API ─────────────────────────────────────────────────────────────


class TestSearchWeb:
    """Test the unified search_web() function."""

    @patch("alive.integrations.web_search._get_backend", return_value="duckduckgo")
    @patch("alive.integrations.web_search._search_ddg")
    def test_search_web_uses_detected_backend(self, mock_ddg, mock_backend):
        from alive.integrations.web_search import search_web

        mock_ddg.return_value = [{"title": "T", "url": "U", "snippet": "S"}]
        results = search_web("query")
        mock_ddg.assert_called_once_with("query", 5)
        assert len(results) == 1

    @patch("alive.integrations.web_search._get_backend", return_value="tavily")
    @patch("alive.integrations.web_search._search_tavily")
    def test_search_web_uses_tavily_when_configured(self, mock_tavily, mock_backend):
        from alive.integrations.web_search import search_web

        mock_tavily.return_value = []
        search_web("query", max_results=3)
        mock_tavily.assert_called_once_with("query", 3)


class TestSearchAndRead:
    """Test search_and_read() — search + fetch."""

    @patch("alive.integrations.web_search.search_web")
    def test_returns_no_results_message(self, mock_search):
        from alive.integrations.web_search import search_and_read

        mock_search.return_value = []
        assert search_and_read("nothing") == "No results found."

    @patch("alive.integrations.web_search.search_web")
    def test_fetches_top_result(self, mock_search):
        from alive.integrations.web_search import search_and_read

        mock_search.return_value = [
            {"title": "Test", "url": "https://example.com", "snippet": "Test"},
        ]

        mock_resp = MagicMock()
        mock_resp.text = "<html><body><p>Hello World content</p></body></html>"
        mock_resp.raise_for_status = MagicMock()

        with patch("httpx.get", return_value=mock_resp):
            text = search_and_read("test")

        assert "Hello World content" in text

    @patch("alive.integrations.web_search.search_web")
    def test_caps_content_at_5000_chars(self, mock_search):
        from alive.integrations.web_search import search_and_read

        mock_search.return_value = [
            {"title": "Test", "url": "https://example.com", "snippet": "Test"},
        ]

        mock_resp = MagicMock()
        mock_resp.text = "<html><body>" + "x" * 10000 + "</body></html>"
        mock_resp.raise_for_status = MagicMock()

        with patch("httpx.get", return_value=mock_resp):
            text = search_and_read("test")

        assert len(text) <= 5000


# ── Display ────────────────────────────────────────────────────────────────


class TestDisplaySearchResults:
    """Test rich display of search results."""

    def test_display_empty_results(self):
        from rich.console import Console

        from alive.integrations.web_search import display_search_results

        con = Console(file=MagicMock(), force_terminal=True)
        display_search_results([], console=con)  # Should not raise

    def test_display_with_results(self):
        from rich.console import Console

        from alive.integrations.web_search import display_search_results

        con = Console(file=MagicMock(), force_terminal=True)
        results = [
            {"title": "T1", "url": "https://a.com", "snippet": "S1"},
            {"title": "T2", "url": "https://b.com", "snippet": "S2"},
        ]
        display_search_results(results, console=con)  # Should not raise
