"""
Tests for PubMed search engine.

Tests cover:
- Initialization
- Query optimization
- Result count retrieval
- Adaptive search strategies
- Article metadata extraction
- Full text retrieval
"""

from unittest.mock import Mock, patch


class TestPubMedSearchEngineInit:
    """Tests for PubMedSearchEngine initialization."""

    def test_default_initialization(self):
        """PubMedSearchEngine initializes with default values."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        assert engine.max_results >= 25  # min is 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.optimize_queries is True

    def test_custom_initialization(self):
        """PubMedSearchEngine accepts 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_base_urls_set(self):
        """PubMedSearchEngine sets correct base URLs."""
        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" in engine.search_url
        assert "esummary" in engine.summary_url
        assert "efetch" in engine.fetch_url

    def test_is_public_and_scientific(self):
        """PubMedSearchEngine is marked as public and scientific."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        assert PubMedSearchEngine.is_public is True
        assert PubMedSearchEngine.is_scientific is True

    def test_context_inclusion_flags(self):
        """PubMedSearchEngine accepts context inclusion flags."""
        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,
            include_keywords_in_context=False,
            include_doi_in_context=True,
            include_pmid_in_context=True,
        )

        assert engine.include_publication_type_in_context is False
        assert engine.include_journal_in_context is False
        assert engine.include_doi_in_context is True
        assert engine.include_pmid_in_context is True


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

    def test_extract_simple_terms(self):
        """Extracts simple terms from query."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._extract_core_terms("cancer treatment options")

        assert "cancer" in result
        assert "treatment" in result
        assert "options" in result

    def test_removes_field_tags(self):
        """Removes [Field] tags from query."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._extract_core_terms("cancer[Title] AND treatment[Mesh]")

        assert "[Title]" not in result
        assert "[Mesh]" not in result

    def test_removes_operators(self):
        """Removes AND/OR/NOT operators."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._extract_core_terms(
            "cancer AND treatment OR therapy NOT placebo"
        )

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

    def test_filters_short_terms(self):
        """Filters out terms shorter than 4 characters."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._extract_core_terms("the cancer is a disease")

        # Check word-by-word, not substring
        result_words = result.split()
        assert "the" not in result_words
        assert "is" not in result_words
        assert "a" not in result_words
        assert "cancer" in result_words

    def test_limits_to_five_terms(self):
        """Limits output to 5 terms maximum."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._extract_core_terms(
            "cancer treatment therapy options medicine clinical trials research"
        )

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


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

    def test_expand_from_months_to_six_months(self):
        """Expands from less than 6 months to 6 months."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        # Note: The regex pattern in _expand_time_window uses [pdat] as char class
        # To match, we need a trailing char from [pdat]
        result = engine._expand_time_window('"last 3 months"p')

        assert "6 months" in result

    def test_expand_from_six_months_to_year(self):
        """Expands from 6 months to 1 year."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._expand_time_window('"last 6 months"p')

        assert "1 year" in result

    def test_expand_from_year_to_two_years(self):
        """Expands from 1 year to 2 years."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._expand_time_window('"last 1 year"p')

        assert "2 years" in result

    def test_expand_from_two_years_to_five_years(self):
        """Expands from 2 years to 5 years."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._expand_time_window('"last 2 years"p')

        assert "5 years" in result

    def test_invalid_format_returns_ten_years(self):
        """Invalid format returns 10 years default."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._expand_time_window("invalid filter")

        assert "10 years" in result

    def test_standard_pdat_format_returns_ten_years(self):
        """Standard [pdat] format triggers fallback due to regex pattern."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        # The regex [pdat] in the implementation is a char class, not literal [pdat]
        # So standard format doesn't match and returns fallback
        result = engine._expand_time_window('"last 3 months"[pdat]')

        assert "10 years" in result


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

    def test_removes_mesh_tags(self):
        """Removes [Mesh] tags from query."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._simplify_query(
            "cancer[Mesh] AND treatment[Title/Abstract]"
        )

        assert "[Mesh]" not in result
        assert "cancer" in result

    def test_removes_publication_type_tags(self):
        """Removes [Publication Type] tags from query."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._simplify_query('"Review"[Publication Type] AND cancer')

        assert "[Publication Type]" not in result

    def test_keeps_title_abstract_tags(self):
        """Keeps [Title/Abstract] tags."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._simplify_query("cancer[Title/Abstract]")

        assert "[Title/Abstract]" in result

    def test_cleans_double_spaces(self):
        """Cleans up double spaces."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._simplify_query("cancer  AND   treatment")

        assert "  " not in result


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

    def test_detects_historical_terms_without_llm(self):
        """Detects historical terms without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        assert (
            engine._is_historical_focused("history of cancer treatment") is True
        )
        assert (
            engine._is_historical_focused("early development of vaccines")
            is True
        )
        assert engine._is_historical_focused("origins of disease") is True

    def test_detects_non_historical_without_llm(self):
        """Detects non-historical queries without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        # Note: "development" is in historical_terms, so avoid it
        assert engine._is_historical_focused("recent cancer treatment") is False
        assert engine._is_historical_focused("new vaccine research") is False
        assert (
            engine._is_historical_focused("current therapies for diabetes")
            is False
        )

    def test_detects_historical_years(self):
        """Detects historical years in query."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        assert engine._is_historical_focused("cancer treatment in 1990") is True
        assert engine._is_historical_focused("medical advances 1950") is True

    def test_uses_llm_when_available(self):
        """Uses LLM when available for determination."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="yes")

        engine = PubMedSearchEngine(llm=mock_llm)
        result = engine._is_historical_focused("some query")

        assert result is True
        mock_llm.invoke.assert_called_once()


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

    def test_returns_original_without_llm(self):
        """Returns original query without LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)
        result = engine._optimize_query_for_pubmed("cancer treatment")

        assert result == "cancer treatment"

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

        mock_llm = Mock()
        engine = PubMedSearchEngine(llm=mock_llm, optimize_queries=False)
        result = engine._optimize_query_for_pubmed("cancer treatment")

        assert result == "cancer treatment"
        mock_llm.invoke.assert_not_called()

    def test_optimizes_with_llm(self):
        """Optimizes query using LLM."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="cancer[Title/Abstract] AND treatment[Title/Abstract]"
        )

        engine = PubMedSearchEngine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer treatment")

        assert "cancer" in result
        mock_llm.invoke.assert_called_once()

    def test_handles_llm_error(self):
        """Handles LLM error gracefully."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("LLM error")

        engine = PubMedSearchEngine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer treatment")

        assert result == "cancer treatment"

    def test_standardizes_field_tags(self):
        """Standardizes field tag case."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="cancer[mesh] AND treatment[title/abstract]"
        )

        engine = PubMedSearchEngine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer treatment")

        assert "[Mesh]" in result or "[Title/Abstract]" in result


class TestGetResultCount:
    """Tests for _get_result_count method."""

    def test_returns_count_on_success(self):
        """Returns count on successful API call."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.json.return_value = {
                "esearchresult": {"count": "100"}
            }
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_result_count("cancer")

            assert result == 100

    def test_returns_zero_on_error(self):
        """Returns 0 on API error."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_get.side_effect = Exception("API error")

            result = engine._get_result_count("cancer")

            assert result == 0

    def test_includes_api_key_when_set(self):
        """Includes API key in request when set."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

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

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.json.return_value = {
                "esearchresult": {"count": "100"}
            }
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            engine._get_result_count("cancer")

            call_kwargs = mock_get.call_args[1]
            assert call_kwargs["params"]["api_key"] == "test-key"


class TestSearchPubmed:
    """Tests for _search_pubmed method."""

    def test_returns_pmids_on_success(self):
        """Returns list of PMIDs on success."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.json.return_value = {
                "esearchresult": {"idlist": ["12345", "67890"]}
            }
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._search_pubmed("cancer treatment")

            assert result == ["12345", "67890"]

    def test_returns_empty_on_error(self):
        """Returns empty list on error."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_get.side_effect = Exception("API error")

            result = engine._search_pubmed("cancer treatment")

            assert result == []


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

    def test_uses_tight_filter_for_high_volume(self):
        """Uses tight time filter for high volume topics."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        with patch.object(engine, "_get_result_count", return_value=10000):
            # Return >= 5 results to avoid time window expansion
            with patch.object(
                engine, "_search_pubmed", return_value=["1", "2", "3", "4", "5"]
            ) as mock_search:
                _, strategy = engine._adaptive_search("cancer")

                assert strategy == "high_volume"
                # Check that time filter was applied
                call_args = mock_search.call_args[0][0]
                assert "1 year" in call_args

    def test_uses_moderate_filter_for_common_topic(self):
        """Uses moderate time filter for common topics."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        with patch.object(engine, "_get_result_count", return_value=2000):
            # Return >= 5 results to avoid time window expansion
            with patch.object(
                engine, "_search_pubmed", return_value=["1", "2", "3", "4", "5"]
            ) as mock_search:
                _, strategy = engine._adaptive_search("cancer")

                assert strategy == "common_topic"
                call_args = mock_search.call_args[0][0]
                assert "3 years" in call_args

    def test_no_filter_for_historical_query(self):
        """Uses no time filter for historical queries."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        with patch.object(engine, "_get_result_count", return_value=100):
            with patch.object(
                engine, "_search_pubmed", return_value=["123"]
            ) as mock_search:
                _, strategy = engine._adaptive_search(
                    "history of medicine 1900"
                )

                assert strategy == "historical_focus"
                # Should not contain time filter
                call_args = mock_search.call_args[0][0]
                assert "[pdat]" not in call_args

    def test_expands_time_window_when_few_results(self):
        """Expands time window when fewer than 5 results returned."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None)

        with patch.object(engine, "_get_result_count", return_value=10000):
            # Return < 5 results on first call, more on second (expanded) call
            call_count = 0

            def side_effect_search(query):
                nonlocal call_count
                call_count += 1
                if call_count == 1:
                    return ["1", "2"]  # Initial call: < 5 results
                else:
                    return [
                        "1",
                        "2",
                        "3",
                        "4",
                        "5",
                        "6",
                    ]  # Expanded: more results

            with patch.object(
                engine, "_search_pubmed", side_effect=side_effect_search
            ):
                _, strategy = engine._adaptive_search("cancer")

                # Should expand since < 5 results initially and more on expansion
                assert "expanded" in strategy


class TestSearchByAuthor:
    """Tests for search_by_author method."""

    def test_formats_author_query(self):
        """Formats author query correctly."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch.object(
            engine, "_search_pubmed", return_value=[]
        ) as mock_search:
            with patch.object(
                engine, "_get_article_summaries", return_value=[]
            ):
                engine.search_by_author("Smith J")

                call_args = mock_search.call_args[0][0]
                assert "Smith J" in call_args
                assert "[Author]" in call_args


class TestSearchByJournal:
    """Tests for search_by_journal method."""

    def test_formats_journal_query(self):
        """Formats journal query correctly."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch.object(
            engine, "_search_pubmed", return_value=[]
        ) as mock_search:
            with patch.object(
                engine, "_get_article_summaries", return_value=[]
            ):
                engine.search_by_journal("Nature")

                call_args = mock_search.call_args[0][0]
                assert "Nature" in call_args
                assert "[Journal]" in call_args


class TestSearchRecent:
    """Tests for search_recent method."""

    def test_sets_days_limit(self):
        """Sets days_limit when searching recent articles."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        original_days_limit = engine.days_limit

        # Mock the run method to capture days_limit during execution
        days_limit_during_search = None

        def capture_days_limit(query):
            nonlocal days_limit_during_search
            days_limit_during_search = engine.days_limit
            return []

        with patch.object(engine, "run", side_effect=capture_days_limit):
            engine.search_recent("cancer", days=30)

            # Days limit should have been set during the search
            assert days_limit_during_search == 30
            # Days limit should be restored after the search
            assert engine.days_limit == original_days_limit

    def test_restores_original_values(self):
        """Restores original values after search."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(max_results=50, days_limit=7)

        with patch.object(engine, "run", return_value=[]):
            engine.search_recent("cancer", days=30, max_results=100)

            # Original values should be restored
            assert engine.max_results >= 50
            assert engine.days_limit == 7


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

    def test_returns_summaries_on_success(self):
        """Returns article summaries on successful API call."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        mock_response_data = {
            "result": {
                "12345": {
                    "title": "Test Article",
                    "pubdate": "2024 Jan",
                    "source": "Test Journal",
                    "authors": [{"name": "Smith J"}, {"name": "Doe A"}],
                    "fulljournalname": "Test Journal Name",
                    "volume": "10",
                    "issue": "2",
                    "pages": "100-110",
                    "articleids": [{"idtype": "doi", "value": "10.1234/test"}],
                    "pubtype": ["Journal Article"],
                },
                "uids": ["12345"],
            }
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.json.return_value = mock_response_data
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

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

            assert len(result) == 1
            assert result[0]["title"] == "Test Article"
            assert result[0]["journal"] == "Test Journal Name"
            assert "pubmed.ncbi.nlm.nih.gov" in result[0]["link"]

    def test_returns_empty_for_empty_id_list(self):
        """Returns empty list for empty ID list."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._get_article_summaries([])

        assert result == []

    def test_returns_empty_on_error(self):
        """Returns empty list on API error."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_get.side_effect = Exception("API error")

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

            assert result == []

    def test_raises_rate_limit_error_on_429(self):
        """Raises RateLimitError on 429 response."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_get.side_effect = Exception("429 Too Many Requests")

            try:
                engine._get_article_summaries(["12345"])
            except RateLimitError:
                pass  # Expected


class TestGetArticleAbstracts:
    """Tests for _get_article_abstracts method."""

    def test_returns_abstracts_from_xml(self):
        """Returns abstracts parsed from XML."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>12345</PMID>
                    <Article>
                        <Abstract>
                            <AbstractText>This is the abstract content.</AbstractText>
                        </Abstract>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_article_abstracts(["12345"])

            assert "12345" in result
            assert "abstract content" in result["12345"]

    def test_returns_empty_for_empty_id_list(self):
        """Returns empty dict for empty ID list."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._get_article_abstracts([])

        assert result == {}

    def test_handles_structured_abstract(self):
        """Handles abstract with multiple labeled sections."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>12345</PMID>
                    <Article>
                        <Abstract>
                            <AbstractText Label="BACKGROUND">Background info.</AbstractText>
                            <AbstractText Label="METHODS">Methods info.</AbstractText>
                            <AbstractText Label="RESULTS">Results info.</AbstractText>
                        </Abstract>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_article_abstracts(["12345"])

            assert "12345" in result
            assert "BACKGROUND" in result["12345"]
            assert "METHODS" in result["12345"]
            assert "RESULTS" in result["12345"]


class TestGetArticleDetailedMetadata:
    """Tests for _get_article_detailed_metadata method."""

    def test_extracts_mesh_terms(self):
        """Extracts MeSH terms from XML."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>12345</PMID>
                    <MeshHeadingList>
                        <MeshHeading>
                            <DescriptorName>Neoplasms</DescriptorName>
                        </MeshHeading>
                        <MeshHeading>
                            <DescriptorName>Humans</DescriptorName>
                        </MeshHeading>
                    </MeshHeadingList>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_article_detailed_metadata(["12345"])

            assert "12345" in result
            assert "mesh_terms" in result["12345"]
            assert "Neoplasms" in result["12345"]["mesh_terms"]
            assert "Humans" in result["12345"]["mesh_terms"]

    def test_extracts_publication_types(self):
        """Extracts publication types from XML."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>12345</PMID>
                    <Article>
                        <PublicationTypeList>
                            <PublicationType>Clinical Trial</PublicationType>
                            <PublicationType>Randomized Controlled Trial</PublicationType>
                        </PublicationTypeList>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_article_detailed_metadata(["12345"])

            assert "12345" in result
            assert "publication_types" in result["12345"]
            assert "Clinical Trial" in result["12345"]["publication_types"]

    def test_extracts_keywords(self):
        """Extracts keywords from XML."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>12345</PMID>
                    <KeywordList>
                        <Keyword>cancer</Keyword>
                        <Keyword>treatment</Keyword>
                    </KeywordList>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_article_detailed_metadata(["12345"])

            assert "12345" in result
            assert "keywords" in result["12345"]
            assert "cancer" in result["12345"]["keywords"]

    def test_returns_empty_for_empty_id_list(self):
        """Returns empty dict for empty ID list."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()
        result = engine._get_article_detailed_metadata([])

        assert result == {}


class TestCreateEnrichedContent:
    """Tests for _create_enriched_content method."""

    def test_adds_study_type_prefix(self):
        """Adds study type prefix for significant publication types."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        result = {
            "publication_types": [
                "Randomized Controlled Trial",
                "Journal Article",
            ]
        }
        base_content = "This is the abstract."

        enriched = engine._create_enriched_content(result, base_content)

        assert "Randomized Controlled Trial" in enriched
        assert "This is the abstract." in enriched

    def test_adds_mesh_terms_footer(self):
        """Adds MeSH terms to metadata footer."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        result = {"mesh_terms": ["Neoplasms", "Humans", "Treatment"]}
        base_content = "Abstract content."

        enriched = engine._create_enriched_content(result, base_content)

        assert "Medical Topics (MeSH)" in enriched
        assert "Neoplasms" in enriched

    def test_adds_funding_information(self):
        """Adds funding/grant information to footer."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        result = {
            "grants": [{"agency": "NIH", "id": "R01-12345"}],
        }
        base_content = "Abstract."

        enriched = engine._create_enriched_content(result, base_content)

        assert "NIH" in enriched
        assert "R01-12345" in enriched

    def test_returns_base_content_with_no_metadata(self):
        """Returns base content when no metadata present."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        result = {}
        base_content = "Simple abstract."

        enriched = engine._create_enriched_content(result, base_content)

        assert "Simple abstract." in enriched


class TestFindPmcIds:
    """Tests for _find_pmc_ids method."""

    def test_returns_empty_when_full_text_disabled(self):
        """Returns empty when get_full_text is False."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(get_full_text=False)

        result = engine._find_pmc_ids(["12345"])

        assert result == {}

    def test_returns_empty_for_empty_list(self):
        """Returns empty for empty PMID list."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(get_full_text=True)

        result = engine._find_pmc_ids([])

        assert result == {}

    def test_returns_pmid_to_pmcid_mapping(self):
        """Returns mapping of PMIDs to PMC IDs."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(get_full_text=True)

        json_response = {
            "linksets": [
                {
                    "ids": [12345],
                    "linksetdbs": [
                        {"linkname": "pubmed_pmc", "links": [9876543]}
                    ],
                }
            ]
        }

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.json.return_value = json_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._find_pmc_ids(["12345"])

            assert "12345" in result
            assert result["12345"] == "PMC9876543"


class TestGetPmcFullText:
    """Tests for _get_pmc_full_text method."""

    def test_extracts_full_text_from_xml(self):
        """Extracts full text content from PMC XML."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        xml_response = """<?xml version="1.0"?>
        <pmc-articleset>
            <article>
                <front>
                    <article-meta>
                        <article-title>Test Article Title</article-title>
                    </article-meta>
                </front>
                <body>
                    <sec>
                        <title>Introduction</title>
                        <p>Introduction paragraph content.</p>
                    </sec>
                    <sec>
                        <title>Methods</title>
                        <p>Methods paragraph content.</p>
                    </sec>
                </body>
            </article>
        </pmc-articleset>
        """

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_response = Mock()
            mock_response.text = xml_response
            mock_response.raise_for_status = Mock()
            mock_get.return_value = mock_response

            result = engine._get_pmc_full_text("PMC12345")

            assert "Test Article Title" in result
            assert "Introduction" in result
            assert "Methods" in result

    def test_returns_empty_on_error(self):
        """Returns empty string on error."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch(
            "local_deep_research.web_search_engines.engines.search_engine_pubmed.safe_get"
        ) as mock_get:
            mock_get.side_effect = Exception("API error")

            result = engine._get_pmc_full_text("PMC12345")

            assert result == ""


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

    def test_returns_previews_for_results(self):
        """Returns preview list for search results."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None, optimize_queries=False)

        with patch.object(
            engine,
            "_adaptive_search",
            return_value=(["12345"], "test_strategy"),
        ):
            with patch.object(
                engine,
                "_get_article_summaries",
                return_value=[
                    {
                        "id": "12345",
                        "title": "Test Article",
                        "link": "https://pubmed.ncbi.nlm.nih.gov/12345/",
                        "pubdate": "2024 Jan",
                        "journal": "Test Journal",
                        "authors": ["Smith J"],
                        "doi": "10.1234/test",
                    }
                ],
            ):
                with patch.object(
                    engine,
                    "_get_article_abstracts",
                    return_value={"12345": "Test abstract."},
                ):
                    result = engine._get_previews("test query")

                    assert len(result) == 1
                    assert result[0]["title"] == "Test Article"
                    assert result[0]["_pmid"] == "12345"

    def test_returns_empty_when_no_results(self):
        """Returns empty list when no search results."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(llm=None, optimize_queries=False)

        with patch.object(
            engine, "_adaptive_search", return_value=([], "test_strategy")
        ):
            with patch.object(
                engine, "_simplify_query", return_value="simple query"
            ):
                result = engine._get_previews("test query")

                assert result == []


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

    def test_adds_abstracts_to_results(self):
        """Adds abstracts to relevant items."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(
            get_abstracts=True, get_full_text=False, llm=None
        )

        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"12345": "Full abstract content."},
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value={}
            ):
                with patch.object(engine, "_find_pmc_ids", return_value={}):
                    items = [
                        {
                            "_pmid": "12345",
                            "title": "Test Article",
                            "snippet": "Short snippet",
                        }
                    ]

                    result = engine._get_full_content(items)

                    assert len(result) == 1
                    assert "abstract" in result[0]
                    assert result[0]["abstract"] == "Full abstract content."

    def test_adds_publication_types_from_metadata(self):
        """Adds publication types from detailed metadata."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(
            get_abstracts=True, get_full_text=False, llm=None
        )

        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"12345": "Abstract."},
        ):
            with patch.object(
                engine,
                "_get_article_detailed_metadata",
                return_value={
                    "12345": {
                        "publication_types": ["Clinical Trial"],
                        "mesh_terms": ["Neoplasms"],
                    }
                },
            ):
                with patch.object(engine, "_find_pmc_ids", return_value={}):
                    items = [
                        {
                            "_pmid": "12345",
                            "title": "Test",
                            "snippet": "Snippet",
                        }
                    ]

                    result = engine._get_full_content(items)

                    assert "publication_types" in result[0]
                    assert "Clinical Trial" in result[0]["publication_types"]


class TestAdvancedSearch:
    """Tests for advanced_search method."""

    def test_formats_field_specific_query(self):
        """Formats field-specific query correctly."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine()

        with patch.object(
            engine, "_search_pubmed", return_value=[]
        ) as mock_search:
            with patch.object(
                engine, "_get_article_summaries", return_value=[]
            ):
                engine.advanced_search(
                    {"Author": "Smith J", "Journal": "Nature"}
                )

                call_args = mock_search.call_args[0][0]
                assert "Smith J[Author]" in call_args
                assert "Nature[Journal]" in call_args
                assert " AND " in call_args

    def test_respects_max_results_parameter(self):
        """Respects max_results parameter."""
        from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
            PubMedSearchEngine,
        )

        engine = PubMedSearchEngine(max_results=50)

        # Mock the run method to capture max_results during execution
        max_results_during_search = None

        def capture_max_results(query):
            nonlocal max_results_during_search
            max_results_during_search = engine.max_results
            return []

        with patch.object(engine, "run", side_effect=capture_max_results):
            engine.advanced_search({"Title": "cancer"}, max_results=100)

            assert max_results_during_search == 100
            # Original should be restored
            assert engine.max_results >= 50
