"""
Comprehensive tests for the PubMed search engine.
Tests initialization, search functionality, error handling, and rate limiting.
"""

import pytest
from unittest.mock import Mock


class TestPubMedSearchEngineInit:
    """Tests for PubMed search engine initialization."""

    def test_init_default_parameters(self):
        """Test initialization with default parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        # Default max_results is 10, but PubMed forces minimum of 25
        assert engine.max_results >= 25
        assert engine.api_key is None
        assert engine.days_limit is None
        assert engine.get_abstracts is True
        assert engine.get_full_text is False
        assert engine.is_public is True
        assert engine.is_scientific is True

    def test_init_custom_parameters(self):
        """Test initialization with custom parameters."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(
            max_results=50,
            api_key="test_api_key",
            days_limit=30,
            get_abstracts=False,
            get_full_text=True,
            full_text_limit=5,
        )

        assert engine.max_results == 50
        assert engine.api_key == "test_api_key"
        assert engine.days_limit == 30
        assert engine.get_abstracts is False
        assert engine.get_full_text is True
        assert engine.full_text_limit == 5

    def test_api_urls_configured(self):
        """Test that API URLs are correctly configured."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        assert "eutils.ncbi.nlm.nih.gov" in engine.base_url
        assert "esearch.fcgi" in engine.search_url
        assert "esummary.fcgi" in engine.summary_url
        assert "efetch.fcgi" in engine.fetch_url


class TestPubMedSearchExecution:
    """Tests for PubMed search execution."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(max_results=10)

    def test_search_pubmed_success(self, pubmed_engine, monkeypatch):
        """Test successful PubMed search."""
        # Mock the safe_get function
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "esearchresult": {
                "count": "2",
                "retmax": "10",
                "idlist": ["12345678", "87654321"],
            }
        }

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        # Call the search method
        pmids = pubmed_engine._search_pubmed("machine learning")

        assert len(pmids) == 2
        assert "12345678" in pmids
        assert "87654321" in pmids

    def test_search_pubmed_empty_results(self, pubmed_engine, monkeypatch):
        """Test PubMed search with no results."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "esearchresult": {
                "count": "0",
                "retmax": "10",
                "idlist": [],
            }
        }

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        pmids = pubmed_engine._search_pubmed("nonexistent query xyz123")
        assert pmids == []

    def test_search_pubmed_with_api_key(self, monkeypatch):
        """Test that API key is included in request when provided."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(api_key="my_test_api_key")

        # Track the params passed to safe_get
        captured_params = {}

        def mock_safe_get(url, params=None, **kwargs):
            captured_params.update(params or {})
            mock_resp = Mock()
            mock_resp.status_code = 200
            mock_resp.raise_for_status = Mock()
            mock_resp.json.return_value = {
                "esearchresult": {"count": "0", "idlist": []}
            }
            return mock_resp

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            mock_safe_get,
        )

        engine._search_pubmed("test query")
        assert captured_params.get("api_key") == "my_test_api_key"

    def test_search_pubmed_with_date_limit(self, monkeypatch):
        """Test that date limit is included in request when provided."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(days_limit=30)

        captured_params = {}

        def mock_safe_get(url, params=None, **kwargs):
            captured_params.update(params or {})
            mock_resp = Mock()
            mock_resp.status_code = 200
            mock_resp.raise_for_status = Mock()
            mock_resp.json.return_value = {
                "esearchresult": {"count": "0", "idlist": []}
            }
            return mock_resp

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            mock_safe_get,
        )

        engine._search_pubmed("test query")
        assert captured_params.get("reldate") == 30
        assert captured_params.get("datetype") == "pdat"


class TestPubMedErrorHandling:
    """Tests for PubMed search error handling."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(max_results=10)

    def test_search_handles_network_error(self, pubmed_engine, monkeypatch):
        """Test that network errors are handled gracefully."""
        from requests.exceptions import ConnectionError

        def mock_safe_get(*args, **kwargs):
            raise ConnectionError("Network unreachable")

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            mock_safe_get,
        )

        # Should return empty list on error
        result = pubmed_engine._search_pubmed("test query")
        assert result == []

    def test_search_handles_timeout_error(self, pubmed_engine, monkeypatch):
        """Test that timeout errors are handled gracefully."""
        from requests.exceptions import Timeout

        def mock_safe_get(*args, **kwargs):
            raise Timeout("Request timed out")

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            mock_safe_get,
        )

        result = pubmed_engine._search_pubmed("test query")
        assert result == []

    def test_search_handles_http_error(self, pubmed_engine, monkeypatch):
        """Test that HTTP errors are handled gracefully."""
        from requests.exceptions import HTTPError

        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.raise_for_status.side_effect = HTTPError("Server error")

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._search_pubmed("test query")
        assert result == []

    def test_search_handles_invalid_json(self, pubmed_engine, monkeypatch):
        """Test that invalid JSON responses are handled gracefully."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.side_effect = ValueError("Invalid JSON")

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._search_pubmed("test query")
        assert result == []


class TestPubMedResultCount:
    """Tests for getting result count."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(max_results=10)

    def test_get_result_count_success(self, pubmed_engine, monkeypatch):
        """Test getting result count."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {"esearchresult": {"count": "1500"}}

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        count = pubmed_engine._get_result_count("cancer treatment")
        assert count == 1500

    def test_get_result_count_error(self, pubmed_engine, monkeypatch):
        """Test getting result count handles errors."""
        from requests.exceptions import ConnectionError

        def mock_safe_get(*args, **kwargs):
            raise ConnectionError("Network error")

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            mock_safe_get,
        )

        count = pubmed_engine._get_result_count("test query")
        assert count == 0


class TestPubMedContextOptions:
    """Tests for PubMed context configuration options."""

    def test_context_options_initialization(self):
        """Test that context options are properly initialized."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(
            include_publication_type_in_context=False,
            include_journal_in_context=False,
            include_year_in_context=False,
            include_mesh_terms_in_context=False,
            max_mesh_terms=5,
            max_keywords=5,
        )

        assert engine.include_publication_type_in_context is False
        assert engine.include_journal_in_context is False
        assert engine.include_year_in_context is False
        assert engine.include_mesh_terms_in_context is False
        assert engine.max_mesh_terms == 5
        assert engine.max_keywords == 5

    def test_all_context_options_enabled(self):
        """Test initialization with all context options enabled."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(
            include_authors_in_context=True,
            include_full_date_in_context=True,
            include_doi_in_context=True,
            include_pmid_in_context=True,
            include_pmc_availability_in_context=True,
            include_citation_in_context=True,
            include_language_in_context=True,
        )

        assert engine.include_authors_in_context is True
        assert engine.include_full_date_in_context is True
        assert engine.include_doi_in_context is True
        assert engine.include_pmid_in_context is True
        assert engine.include_pmc_availability_in_context is True
        assert engine.include_citation_in_context is True
        assert engine.include_language_in_context is True


class TestExtractCoreTerms:
    """Tests for _extract_core_terms method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine()

    def test_removes_field_tags(self, pubmed_engine):
        """Test that field tags like [Mesh] are removed."""
        query = "diabetes[Mesh] AND treatment[tiab]"
        result = pubmed_engine._extract_core_terms(query)

        # The regex \[\w+\] removes single-word field tags like [Mesh], [tiab]
        assert "[Mesh]" not in result
        assert "[tiab]" not in result
        assert "diabetes" in result
        assert "treatment" in result

    def test_removes_boolean_operators(self, pubmed_engine):
        """Test that AND, OR, NOT operators are removed."""
        query = "cancer AND therapy OR treatment NOT placebo"
        result = pubmed_engine._extract_core_terms(query)

        assert "AND" not in result
        assert "OR" not in result
        assert "NOT" not in result

    def test_removes_quotes_and_parentheses(self, pubmed_engine):
        """Test that quotes and parentheses are removed."""
        query = '("machine learning" OR "deep learning") AND neural'
        result = pubmed_engine._extract_core_terms(query)

        assert '"' not in result
        assert "(" not in result
        assert ")" not in result

    def test_filters_short_terms(self, pubmed_engine):
        """Test that terms shorter than 4 chars are filtered."""
        query = "a the and machine learning for AI"
        result = pubmed_engine._extract_core_terms(query)

        # Short words like 'a', 'the', 'and', 'for', 'AI' should be filtered
        assert "machine" in result
        assert "learning" in result

    def test_limits_to_five_terms(self, pubmed_engine):
        """Test that result is limited to 5 terms."""
        query = "term1 term2 term3 term4 term5 term6 term7 term8"
        result = pubmed_engine._extract_core_terms(query)

        terms = result.split()
        assert len(terms) <= 5


class TestExpandTimeWindow:
    """Tests for _expand_time_window method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine()

    def test_returns_valid_time_filter(self, pubmed_engine):
        """Test that a valid time filter is returned."""
        result = pubmed_engine._expand_time_window('"last 3 month"[pdat]')
        # Should return a valid pdat filter
        assert "[pdat]" in result
        assert "last" in result

    def test_invalid_format_returns_10_years(self, pubmed_engine):
        """Test that invalid format returns 10 years."""
        result = pubmed_engine._expand_time_window("invalid filter")
        assert "10 years" in result

    def test_returns_string_format(self, pubmed_engine):
        """Test that result is a string."""
        result = pubmed_engine._expand_time_window('"last 1 year"[pdat]')
        assert isinstance(result, str)
        assert len(result) > 0


class TestSimplifyQuery:
    """Tests for _simplify_query method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine()

    def test_removes_mesh_tags(self, pubmed_engine):
        """Test that [Mesh] tags are removed."""
        query = "diabetes[Mesh] AND treatment[Title/Abstract]"
        result = pubmed_engine._simplify_query(query)

        assert "[Mesh]" not in result.lower()
        assert "diabetes" in result
        assert "[Title/Abstract]" in result  # Title/Abstract should be kept

    def test_removes_publication_type_tags(self, pubmed_engine):
        """Test that [Publication Type] tags are removed."""
        query = (
            'review[Publication Type] AND "clinical trial"[Publication Type]'
        )
        result = pubmed_engine._simplify_query(query)

        assert "[Publication Type]" not in result

    def test_cleans_double_spaces(self, pubmed_engine):
        """Test that double spaces are cleaned up."""
        query = "term1  AND   term2"
        result = pubmed_engine._simplify_query(query)

        assert "  " not in result

    def test_returns_original_if_no_simplification(self, pubmed_engine):
        """Test that original is returned if no simplification possible."""
        query = "simple query terms"
        result = pubmed_engine._simplify_query(query)

        # Should return same or similar query
        assert len(result) > 0


class TestIsHistoricalFocused:
    """Tests for _is_historical_focused method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(llm=None)

    def test_detects_history_keyword(self, pubmed_engine):
        """Test detection of 'history' keyword."""
        result = pubmed_engine._is_historical_focused("history of antibiotics")
        assert result is True

    def test_detects_early_keyword(self, pubmed_engine):
        """Test detection of 'early' keyword."""
        result = pubmed_engine._is_historical_focused(
            "early research on vaccines"
        )
        assert result is True

    def test_detects_origins_keyword(self, pubmed_engine):
        """Test detection of 'origins' keyword."""
        result = pubmed_engine._is_historical_focused("origins of insulin")
        assert result is True

    def test_not_historical_for_recent_query(self, pubmed_engine):
        """Test that recent queries are not marked as historical."""
        result = pubmed_engine._is_historical_focused(
            "latest cancer treatments"
        )
        assert result is False

    def test_detects_past_year(self, pubmed_engine):
        """Test detection of past years in query."""
        result = pubmed_engine._is_historical_focused(
            "medical advances in 1950"
        )
        assert result is True


class TestOptimizeQueryForPubMed:
    """Tests for _optimize_query_for_pubmed method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(llm=None, optimize_queries=True)

    def test_returns_original_without_llm(self, pubmed_engine):
        """Test that original query is returned without LLM."""
        query = "what are the effects of aspirin"
        result = pubmed_engine._optimize_query_for_pubmed(query)

        assert result == query

    def test_returns_original_when_optimization_disabled(self):
        """Test that original query is returned when optimization disabled."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=Mock(), optimize_queries=False)
        query = "test query"
        result = engine._optimize_query_for_pubmed(query)

        assert result == query


class TestAdaptiveSearch:
    """Tests for _adaptive_search method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine(llm=None)

    def test_high_volume_uses_1_year_filter(self, pubmed_engine, monkeypatch):
        """Test that high volume topics use 1 year filter."""
        # Mock to return high volume
        monkeypatch.setattr(pubmed_engine, "_get_result_count", lambda q: 10000)
        monkeypatch.setattr(
            pubmed_engine, "_search_pubmed", lambda q: ["id1", "id2", "id3"]
        )

        results, strategy = pubmed_engine._adaptive_search("common topic")

        assert strategy == "high_volume"
        assert len(results) == 3

    def test_moderate_volume_uses_5_year_filter(
        self, pubmed_engine, monkeypatch
    ):
        """Test that moderate volume topics use 5 year filter."""
        monkeypatch.setattr(pubmed_engine, "_get_result_count", lambda q: 500)
        monkeypatch.setattr(
            pubmed_engine, "_search_pubmed", lambda q: ["id1", "id2"]
        )

        results, strategy = pubmed_engine._adaptive_search("moderate topic")

        assert strategy == "moderate_volume"

    def test_rare_topic_uses_10_year_filter(self, pubmed_engine, monkeypatch):
        """Test that rare topics use 10 year filter."""
        monkeypatch.setattr(pubmed_engine, "_get_result_count", lambda q: 50)
        monkeypatch.setattr(pubmed_engine, "_search_pubmed", lambda q: ["id1"])

        results, strategy = pubmed_engine._adaptive_search("rare topic")

        assert strategy == "rare_topic"

    def test_historical_query_no_time_filter(self, pubmed_engine, monkeypatch):
        """Test that historical queries don't use time filter."""
        monkeypatch.setattr(pubmed_engine, "_get_result_count", lambda q: 1000)
        monkeypatch.setattr(pubmed_engine, "_search_pubmed", lambda q: ["id1"])

        results, strategy = pubmed_engine._adaptive_search(
            "history of penicillin"
        )

        assert strategy == "historical_focus"

    def test_expands_time_window_on_few_results(
        self, pubmed_engine, monkeypatch
    ):
        """Test that time window is expanded when few results."""
        call_count = [0]

        def mock_search(query):
            call_count[0] += 1
            if call_count[0] == 1:
                return ["id1"]  # First call returns few results
            return ["id1", "id2", "id3", "id4", "id5"]  # Expanded returns more

        monkeypatch.setattr(pubmed_engine, "_get_result_count", lambda q: 2000)
        monkeypatch.setattr(pubmed_engine, "_search_pubmed", mock_search)

        results, strategy = pubmed_engine._adaptive_search("topic query")

        assert "expanded" in strategy
        assert len(results) == 5


class TestGetArticleSummaries:
    """Tests for _get_article_summaries method."""

    @pytest.fixture
    def pubmed_engine(self):
        """Create a PubMed engine instance."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        return PubMedSearchEngine()

    def test_empty_list_returns_empty(self, pubmed_engine):
        """Test that empty ID list returns empty list."""
        result = pubmed_engine._get_article_summaries([])
        assert result == []

    def test_successful_summary_fetch(self, pubmed_engine, monkeypatch):
        """Test successful article summary fetching."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "result": {
                "12345": {
                    "title": "Test Article Title",
                    "pubdate": "2024 Jan",
                    "source": "Test Journal",
                    "authors": [{"name": "Smith J"}, {"name": "Doe A"}],
                    "fulljournalname": "Test Journal Full Name",
                    "doi": "10.1234/test.123",
                }
            }
        }

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._get_article_summaries(["12345"])

        assert len(result) == 1
        assert result[0]["title"] == "Test Article Title"
        assert result[0]["journal"] == "Test Journal Full Name"
        assert len(result[0]["authors"]) == 2

    def test_extracts_doi_from_articleids(self, pubmed_engine, monkeypatch):
        """Test DOI extraction from articleids when not in main field."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "result": {
                "12345": {
                    "title": "Test",
                    "articleids": [
                        {"idtype": "pubmed", "value": "12345"},
                        {"idtype": "doi", "value": "10.5678/extracted.doi"},
                    ],
                }
            }
        }

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._get_article_summaries(["12345"])

        assert result[0]["doi"] == "10.5678/extracted.doi"

    def test_handles_missing_pmid(self, pubmed_engine, monkeypatch):
        """Test handling of PMID not found in response."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {"result": {}}

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._get_article_summaries(["99999"])

        assert len(result) == 0

    def test_creates_pubmed_link(self, pubmed_engine, monkeypatch):
        """Test that PubMed link is correctly created."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.raise_for_status = Mock()
        mock_response.json.return_value = {
            "result": {"12345": {"title": "Test"}}
        }

        monkeypatch.setattr(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get",
            lambda *args, **kwargs: mock_response,
        )

        result = pubmed_engine._get_article_summaries(["12345"])

        assert result[0]["link"] == "https://pubmed.ncbi.nlm.nih.gov/12345/"
