"""
Tests for the ExaSearchEngine class.

Tests cover:
- Initialization and configuration
- API key handling
- Preview generation
- Full content retrieval
- Rate limit error handling
- Run method
- Search types
- Domain and date filtering
"""

from unittest.mock import Mock, patch
import pytest


class TestExaSearchEngineInit:
    """Tests for ExaSearchEngine initialization."""

    def test_init_with_api_key(self):
        """Initialize with API key."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        assert engine.api_key == "test-api-key"
        assert engine.max_results == 10
        assert engine.search_type == "auto"
        assert engine.include_full_content is True

    def test_init_with_custom_max_results(self):
        """Initialize with custom max_results."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key", max_results=25)

        assert engine.max_results == 25

    def test_init_with_custom_search_type(self):
        """Initialize with custom search_type."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key", search_type="neural")

        assert engine.search_type == "neural"

    def test_init_with_domain_filters(self):
        """Initialize with domain filters."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(
            api_key="test-key",
            include_domains=["example.com", "test.com"],
            exclude_domains=["spam.com"],
        )

        assert engine.include_domains == ["example.com", "test.com"]
        assert engine.exclude_domains == ["spam.com"]

    def test_init_with_date_filters(self):
        """Initialize with date filters."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(
            api_key="test-key",
            start_published_date="2024-01-01",
            end_published_date="2024-12-31",
        )

        assert engine.start_published_date == "2024-01-01"
        assert engine.end_published_date == "2024-12-31"

    def test_init_with_category(self):
        """Initialize with category filter."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key", category="research paper")

        assert engine.category == "research paper"

    def test_init_without_api_key_raises(self):
        """Initialize without API key raises ValueError."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        with pytest.raises(ValueError) as exc_info:
            ExaSearchEngine(settings_snapshot={})

        assert "No valid API key found for Exa" in str(exc_info.value)

    def test_init_with_api_key_from_settings(self):
        """Initialize with API key from settings snapshot."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(
            settings_snapshot={
                "search.engine.web.exa.api_key": "settings-api-key"
            }
        )

        assert engine.api_key == "settings-api-key"

    def test_init_with_llm(self):
        """Initialize with LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_llm = Mock()
        engine = ExaSearchEngine(api_key="test-key", llm=mock_llm)

        assert engine.llm is mock_llm

    def test_init_with_include_full_content_false(self):
        """Initialize with include_full_content=False."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key", include_full_content=False)

        assert engine.include_full_content is False


class TestGetPreviews:
    """Tests for _get_previews method."""

    def test_get_previews_returns_results(self):
        """Get previews returns formatted results."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "id": "https://example1.com",
                    "title": "Result 1",
                    "url": "https://example1.com",
                    "text": "Full text content 1",
                },
                {
                    "id": "https://example2.com",
                    "title": "Result 2",
                    "url": "https://example2.com",
                    "highlights": ["Highlight 1", "Highlight 2"],
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert len(previews) == 2
            assert previews[0]["title"] == "Result 1"
            assert previews[0]["link"] == "https://example1.com"
            assert previews[0]["displayed_link"] == "example1.com"
            assert "Full text content 1" in previews[0]["snippet"]
            assert previews[1]["snippet"] == "Highlight 1 ... Highlight 2"
            assert previews[1]["displayed_link"] == "example2.com"

    def test_get_previews_summary_as_snippet(self):
        """Get previews uses summary field as snippet when no highlights."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "id": "https://example.com",
                    "title": "Result",
                    "url": "https://example.com",
                    "summary": "A brief summary of the page",
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews[0]["snippet"] == "A brief summary of the page"

    def test_get_previews_optional_fields(self):
        """Get previews includes optional fields when present."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "id": "https://example.com",
                    "title": "Result",
                    "url": "https://example.com",
                    "text": "Content",
                    "publishedDate": "2024-06-15",
                    "author": "Test Author",
                    "score": 0.95,
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews[0]["published_date"] == "2024-06-15"
            assert previews[0]["author"] == "Test Author"
            assert previews[0]["score"] == 0.95

    def test_get_previews_rate_limit_in_request_exception(self):
        """Get previews detects rate limit patterns in request exceptions."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )
        import requests

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            side_effect=requests.exceptions.RequestException(
                "429 Too Many Requests"
            ),
        ):
            engine = ExaSearchEngine(api_key="test-key")

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

    def test_get_previews_with_domain_filters(self):
        """Get previews sends domain filters to API."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(
                api_key="test-key",
                include_domains=["example.com"],
                exclude_domains=["spam.com"],
            )
            engine._get_previews("test query")

            # Check that domain filters were sent
            call_args = mock_post.call_args
            payload = call_args[1]["json"]
            assert payload["includeDomains"] == ["example.com"]
            assert payload["excludeDomains"] == ["spam.com"]

    def test_get_previews_with_date_filters(self):
        """Get previews sends date filters to API."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(
                api_key="test-key",
                start_published_date="2024-01-01",
                end_published_date="2024-12-31",
            )
            engine._get_previews("test query")

            # Check that date filters were sent
            call_args = mock_post.call_args
            payload = call_args[1]["json"]
            assert payload["startPublishedDate"] == "2024-01-01"
            assert payload["endPublishedDate"] == "2024-12-31"

    def test_get_previews_with_category(self):
        """Get previews sends category to API."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(
                api_key="test-key", category="research paper"
            )
            engine._get_previews("test query")

            # Check that category was sent
            call_args = mock_post.call_args
            payload = call_args[1]["json"]
            assert payload["category"] == "research paper"

    def test_get_previews_with_search_type(self):
        """Get previews sends search type to API."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(api_key="test-key", search_type="neural")
            engine._get_previews("test query")

            # Check that search type was sent
            call_args = mock_post.call_args
            payload = call_args[1]["json"]
            assert payload["type"] == "neural"

    def test_get_previews_rate_limit_error(self):
        """Get previews raises RateLimitError on 429."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )
        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_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")

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

    def test_get_previews_empty_results(self):
        """Get previews handles empty results."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews == []

    def test_get_previews_request_exception(self):
        """Get previews handles request exceptions."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )
        import requests

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            side_effect=requests.exceptions.RequestException(
                "Connection error"
            ),
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews == []

    def test_get_previews_unexpected_error(self):
        """Get previews handles unexpected errors."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.side_effect = Exception("Parse error")
        mock_response.raise_for_status = Mock()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews == []


class TestGetFullContent:
    """Tests for _get_full_content method."""

    def test_get_full_content_returns_items(self):
        """Get full content returns processed items using _full_result."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key")

        items = [
            {
                "title": "Result",
                "link": "https://example.com",
                "_full_result": {
                    "title": "Result",
                    "url": "https://example.com",
                    "text": "Full text content",
                },
            }
        ]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert results[0]["title"] == "Result"
        assert results[0]["text"] == "Full text content"
        assert "_full_result" not in results[0]

    def test_get_full_content_without_full_result(self):
        """Get full content handles items without _full_result."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key")

        items = [{"title": "Result", "link": "https://example.com"}]

        results = engine._get_full_content(items)

        assert len(results) == 1
        assert results[0]["title"] == "Result"
        assert results[0]["link"] == "https://example.com"


class TestRun:
    """Tests for run method."""

    def test_run_returns_results(self):
        """Run returns search results."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key")

        with patch.object(
            engine,
            "_get_previews",
            return_value=[
                {
                    "id": "https://example.com",
                    "title": "Result",
                    "snippet": "Snippet",
                    "link": "https://example.com",
                }
            ],
        ):
            with patch.object(
                engine,
                "_get_full_content",
                return_value=[{"title": "Result", "text": "Full"}],
            ):
                results = engine.run("test query")

                assert len(results) == 1

    def test_run_handles_empty_results(self):
        """Run handles empty results."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        engine = ExaSearchEngine(api_key="test-key")

        with patch.object(engine, "_get_previews", return_value=[]):
            results = engine.run("test query")

            assert results == []


class TestClassAttributes:
    """Tests for class attributes."""

    def test_is_public(self):
        """ExaSearchEngine is marked as public."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        assert ExaSearchEngine.is_public is True

    def test_is_generic(self):
        """ExaSearchEngine is marked as generic."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        assert ExaSearchEngine.is_generic is True


class TestContentsPayload:
    """Tests for contents payload in _get_previews."""

    def test_contents_includes_highlights_and_summary(self):
        """Contents payload includes text, highlights, and summary."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(
                api_key="test-key", include_full_content=True
            )
            engine._get_previews("test query")

            payload = mock_post.call_args[1]["json"]
            contents = payload["contents"]
            assert "text" in contents
            assert contents["text"] == {"maxCharacters": 10000}
            assert "highlights" in contents
            assert contents["highlights"] == {
                "maxCharacters": 500,
                "query": "test query",
            }
            assert "summary" in contents
            assert contents["summary"] == {"query": "test query"}

    def test_no_contents_when_full_content_disabled(self):
        """No contents payload when include_full_content is False."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ) as mock_post:
            engine = ExaSearchEngine(
                api_key="test-key", include_full_content=False
            )
            engine._get_previews("test query")

            payload = mock_post.call_args[1]["json"]
            assert "contents" not in payload


class TestHighlightsTypeSafety:
    """Tests for highlights type safety in _get_previews."""

    def test_highlights_as_string_ignored(self):
        """Non-list highlights are ignored, falls through to summary or text."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "title": "Result",
                    "url": "https://example.com",
                    "highlights": "not a list",
                    "text": "Some text content",
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            # Should fall through to text since highlights is not a list
            assert previews[0]["snippet"] == "Some text content"[:500]

    def test_highlights_as_list_used(self):
        """List highlights are properly joined."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "title": "Result",
                    "url": "https://example.com",
                    "highlights": ["First highlight", "Second highlight"],
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert (
                previews[0]["snippet"] == "First highlight ... Second highlight"
            )

    def test_empty_highlights_list_falls_through(self):
        """Empty highlights list falls through to summary or text."""
        from local_deep_research.web_search_engines.engines.search_engine_exa import (
            ExaSearchEngine,
        )

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "results": [
                {
                    "title": "Result",
                    "url": "https://example.com",
                    "highlights": [],
                    "summary": "A summary",
                },
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_exa.safe_post",
            return_value=mock_response,
        ):
            engine = ExaSearchEngine(api_key="test-key")
            previews = engine._get_previews("test query")

            assert previews[0]["snippet"] == "A summary"
