"""
Coverage tests for PubMed search engine.

Tests cover edge cases and logic paths not exercised by the main test file:
- _optimize_query_for_pubmed: quote stripping, explanation prefix removal,
  verbose response extraction, fallback to simple form, unclosed quote fixing,
  publication type tag standardization
- _adaptive_search: moderate volume, rare topic, time filter retry, expanded results
- _expand_time_window: singular month, years 2->5, years 5->10, months 12->2yr
- _get_article_abstracts XML parsing: missing PMID, None abstract text, unlabeled sections
- _get_article_summaries: DOI from articleids, rate limit patterns (503, 403, too many requests)
- _get_full_content PMC integration: full text retrieval, fallback to abstract, snippets-only,
  PMC availability context, mesh/keyword truncation
- _get_previews: author truncation, citation details, language indicator, identifiers,
  date context, separator logic
- _is_historical_focused: LLM error fallback to keyword check
- _search_pubmed: API key inclusion, days_limit params
- _create_enriched_content: affiliations, grants, COI filtering, keywords footer
"""

import pytest
from unittest.mock import Mock, patch

MODULE = "local_deep_research.web_search_engines.engines.search_engine_pubmed"


def _make_engine(**kwargs):
    from local_deep_research.web_search_engines.engines.search_engine_pubmed import (
        PubMedSearchEngine,
    )

    return PubMedSearchEngine(**kwargs)


class TestOptimizeQueryQuoteStripping:
    def test_strips_wrapping_quotes(self):
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content='"cancer[Title/Abstract] AND treatment[Title/Abstract]"'
        )
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer treatment")
        assert not result.startswith('"cancer[Title')
        assert "cancer" in result


class TestOptimizeQueryExplanationPrefixRemoval:
    def test_removes_here_is_prefix(self):
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="Here is the query: cancer[Title/Abstract]"
        )
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer treatment")
        assert "Here is" not in result
        assert "cancer" in result

    def test_removes_this_query_prefix(self):
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="This query searches for: mRNA[Title/Abstract]"
        )
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("mRNA vaccines")
        assert "This query" not in result
        assert "mRNA" in result


class TestOptimizeQueryVerboseResponse:
    def test_extracts_query_from_verbose_response(self):
        mock_llm = Mock()
        verbose = (
            "This query will find articles about vaccines. "
            "(mRNA[Title/Abstract] OR messenger[Title/Abstract]) AND vaccin*[Title/Abstract]. "
            "This approach uses wildcards for broader coverage and focuses on title and abstract fields. "
            "Additional explanation padding text to make it really long and exceed the threshold."
        )
        mock_llm.invoke.return_value = Mock(content=verbose)
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("mRNA vaccines")
        assert "mRNA" in result or "vaccin" in result


class TestOptimizeQueryFallbackToSimpleForm:
    def test_falls_back_to_simple_form_for_very_long_query(self):
        mock_llm = Mock()
        long_response = " ".join(["word"] * 35)
        mock_llm.invoke.return_value = Mock(content=long_response)
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed(
            "cancer immunotherapy checkpoint inhibitors"
        )
        assert "AND" in result
        assert "the" not in result.lower().split(" AND ")


class TestOptimizeQueryUnclosedQuoteFix:
    def test_fixes_unclosed_quote_before_field_tag(self):
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content='"messenger RNA[Title/Abstract]'
        )
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("messenger RNA")
        assert '"messenger RNA"[Title/Abstract]' in result


class TestOptimizeQueryPublicationTypeTag:
    def test_standardizes_publication_type_case(self):
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content='"Review"[publication type] AND cancer[title/abstract]'
        )
        engine = _make_engine(llm=mock_llm, optimize_queries=True)
        result = engine._optimize_query_for_pubmed("cancer reviews")
        assert "[Publication Type]" in result
        assert "[Title/Abstract]" in result


class TestAdaptiveSearchModerateVolume:
    def test_moderate_volume_uses_five_year_filter(self):
        engine = _make_engine(llm=None)
        with patch.object(engine, "_get_result_count", return_value=500):
            with patch.object(
                engine,
                "_search_pubmed",
                return_value=["1", "2", "3", "4", "5"],
            ) as mock_search:
                _, strategy = engine._adaptive_search("rare disease treatment")
                assert strategy == "moderate_volume"
                call_arg = mock_search.call_args[0][0]
                assert "5 years" in call_arg


class TestAdaptiveSearchRareTopic:
    def test_rare_topic_uses_ten_year_filter(self):
        engine = _make_engine(llm=None)
        with patch.object(engine, "_get_result_count", return_value=50):
            with patch.object(
                engine,
                "_search_pubmed",
                return_value=["1", "2", "3", "4", "5"],
            ) as mock_search:
                _, strategy = engine._adaptive_search(
                    "very rare genetic condition"
                )
                assert strategy == "rare_topic"
                call_arg = mock_search.call_args[0][0]
                assert "10 years" in call_arg


class TestAdaptiveSearchTimeFilterRetry:
    def test_retries_without_time_filter(self):
        engine = _make_engine(llm=None)
        with patch.object(engine, "_get_result_count", return_value=10000):
            call_count = 0

            def search_side_effect(query):
                nonlocal call_count
                call_count += 1
                if "[pdat]" in query:
                    return []
                return ["1", "2", "3"]

            with patch.object(
                engine, "_search_pubmed", side_effect=search_side_effect
            ):
                results, strategy = engine._adaptive_search("cancer")
                assert strategy == "no_time_filter"
                assert len(results) == 3


class TestAdaptiveSearchExpandedComparison:
    def test_keeps_original_when_expanded_not_better(self):
        engine = _make_engine(llm=None)
        with patch.object(engine, "_get_result_count", return_value=2000):
            call_count = 0

            def search_side_effect(query):
                nonlocal call_count
                call_count += 1
                if call_count == 1:
                    return ["1", "2", "3"]
                return ["1"]

            with patch.object(
                engine, "_search_pubmed", side_effect=search_side_effect
            ):
                results, strategy = engine._adaptive_search("cancer")
                assert strategy == "common_topic"
                assert results == ["1", "2", "3"]


class TestExpandTimeWindowEdgeCases:
    def test_singular_month_unit(self):
        engine = _make_engine()
        result = engine._expand_time_window('"last 1 month"p')
        assert "6 months" in result

    def test_years_2_to_5(self):
        engine = _make_engine()
        result = engine._expand_time_window('"last 3 years"p')
        assert "5 years" in result

    def test_years_5_to_10(self):
        engine = _make_engine()
        result = engine._expand_time_window('"last 5 years"p')
        assert "10 years" in result

    def test_months_12_to_2yr(self):
        engine = _make_engine()
        result = engine._expand_time_window('"last 12 months"p')
        assert "2 years" in result


class TestGetArticleAbstractsMissingPMID:
    def test_skips_article_without_pmid(self):
        engine = _make_engine()
        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <Article>
                        <Abstract>
                            <AbstractText>Some abstract text.</AbstractText>
                        </Abstract>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>"""
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.text = xml_response
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            result = engine._get_article_abstracts(["99999"])
            assert result == {}


class TestGetArticleAbstractsNoneText:
    def test_handles_none_abstract_text(self):
        engine = _make_engine()
        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>11111</PMID>
                    <Article>
                        <Abstract>
                            <AbstractText />
                        </Abstract>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>"""
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.text = xml_response
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            result = engine._get_article_abstracts(["11111"])
            assert "11111" not in result


class TestGetArticleAbstractsUnlabeledSections:
    def test_concatenates_unlabeled_sections(self):
        engine = _make_engine()
        xml_response = """<?xml version="1.0"?>
        <PubmedArticleSet>
            <PubmedArticle>
                <MedlineCitation>
                    <PMID>22222</PMID>
                    <Article>
                        <Abstract>
                            <AbstractText>First paragraph.</AbstractText>
                            <AbstractText>Second paragraph.</AbstractText>
                        </Abstract>
                    </Article>
                </MedlineCitation>
            </PubmedArticle>
        </PubmedArticleSet>"""
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.text = xml_response
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            result = engine._get_article_abstracts(["22222"])
            assert "22222" in result
            assert "First paragraph." in result["22222"]
            assert "Second paragraph." in result["22222"]


class TestGetArticleSummariesDOIFromArticleIds:
    def test_extracts_doi_from_articleids(self):
        engine = _make_engine()
        response_data = {
            "result": {
                "55555": {
                    "title": "Test",
                    "pubdate": "2024",
                    "epubdate": "",
                    "source": "J",
                    "authors": [],
                    "lastauthor": "",
                    "fulljournalname": "Journal",
                    "volume": "",
                    "issue": "",
                    "pages": "",
                    "issn": "",
                    "essn": "",
                    "pubtype": [],
                    "recordstatus": "",
                    "lang": [],
                    "pmcrefcount": None,
                    "articleids": [
                        {"idtype": "doi", "value": "10.9999/test-doi"}
                    ],
                },
                "uids": ["55555"],
            }
        }
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.json.return_value = response_data
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            result = engine._get_article_summaries(["55555"])
            assert result[0]["doi"] == "10.9999/test-doi"


class TestGetArticleSummariesRateLimitPatterns:
    def test_503_raises_rate_limit(self):
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        engine = _make_engine()
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_get.side_effect = Exception("503 Service Unavailable")
            with pytest.raises(RateLimitError):
                engine._get_article_summaries(["12345"])

    def test_403_raises_rate_limit(self):
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        engine = _make_engine()
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_get.side_effect = Exception("403 Forbidden")
            with pytest.raises(RateLimitError):
                engine._get_article_summaries(["12345"])

    def test_too_many_requests_text_raises_rate_limit(self):
        from local_deep_research.web_search_engines.rate_limiting import (
            RateLimitError,
        )

        engine = _make_engine()
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_get.side_effect = Exception("too many requests error")
            with pytest.raises(RateLimitError):
                engine._get_article_summaries(["12345"])


def _make_preview_item(pmid="99999", title="Test Article", snippet="test"):
    return {
        "id": pmid,
        "title": title,
        "link": f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/",
        "snippet": snippet,
        "_pmid": pmid,
        "_search_strategy": "test",
    }


class TestGetFullContentPMCIntegration:
    def test_full_text_retrieval_via_pmc(self):
        engine = _make_engine(get_full_text=True, get_abstracts=True)
        items = [_make_preview_item(pmid="10001")]
        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"10001": "Abstract text here"},
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value={}
            ):
                with patch.object(
                    engine, "_find_pmc_ids", return_value={"10001": "PMC123456"}
                ):
                    with patch.object(
                        engine,
                        "_get_pmc_full_text",
                        return_value="# Title\n\nFull text body content",
                    ):
                        results = engine._get_full_content(items)
                        assert results[0]["content_type"] == "full_text"
                        assert "Full text body content" in results[0]["content"]
                        assert results[0]["pmcid"] == "PMC123456"

    def test_falls_back_to_abstract_when_pmc_fails(self):
        engine = _make_engine(get_full_text=True, get_abstracts=True)
        items = [_make_preview_item(pmid="10002")]
        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"10002": "Fallback abstract"},
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value={}
            ):
                with patch.object(
                    engine, "_find_pmc_ids", return_value={"10002": "PMC999"}
                ):
                    with patch.object(
                        engine, "_get_pmc_full_text", return_value=""
                    ):
                        results = engine._get_full_content(items)
                        assert results[0]["content_type"] == "abstract"
                        assert "Fallback abstract" in results[0]["content"]


class TestGetFullContentSnippetsOnlyMode:
    def test_snippets_only_mode_uses_abstract(self):
        # Snippets-only behavior is now driven by get_full_text=False; the
        # dead module-level SEARCH_SNIPPETS_ONLY switch this test used to
        # patch was removed in this PR. With full text disabled, no PMC
        # lookup happens and the content is the abstract.
        engine = _make_engine(get_full_text=False, get_abstracts=True)
        items = [_make_preview_item(pmid="10003")]
        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"10003": "Snippet abstract"},
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value={}
            ):
                with patch.object(
                    engine, "_find_pmc_ids", return_value={}
                ) as mock_pmc:
                    results = engine._get_full_content(items)
                    assert results[0]["content_type"] == "abstract"
                    mock_pmc.assert_not_called()


class TestGetFullContentPMCAvailability:
    def test_pmc_availability_added_to_snippet(self):
        engine = _make_engine(
            get_abstracts=True, include_pmc_availability_in_context=True
        )
        items = [_make_preview_item(pmid="10004")]
        metadata = {"10004": {"has_free_full_text": True, "pmc_id": "PMC111"}}
        with patch.object(
            engine,
            "_get_article_abstracts",
            return_value={"10004": "Some abstract"},
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value=metadata
            ):
                results = engine._get_full_content(items)
                assert "[Free Full Text]" in results[0]["snippet"]


class TestGetFullContentMeshKeywordTruncation:
    def test_mesh_terms_truncated(self):
        engine = _make_engine(
            get_abstracts=True,
            include_mesh_terms_in_context=True,
            max_mesh_terms=2,
        )
        items = [_make_preview_item(pmid="10005")]
        metadata = {
            "10005": {"mesh_terms": ["TermA", "TermB", "TermC", "TermD"]}
        }
        with patch.object(
            engine, "_get_article_abstracts", return_value={"10005": "Abstract"}
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value=metadata
            ):
                results = engine._get_full_content(items)
                snippet = results[0]["snippet"]
                assert "TermA" in snippet
                assert "TermB" in snippet
                assert "TermC" not in snippet

    def test_keywords_truncated(self):
        engine = _make_engine(
            get_abstracts=True, include_keywords_in_context=True, max_keywords=1
        )
        items = [_make_preview_item(pmid="10006")]
        metadata = {"10006": {"keywords": ["KW1", "KW2", "KW3"]}}
        with patch.object(
            engine, "_get_article_abstracts", return_value={"10006": "Abstract"}
        ):
            with patch.object(
                engine, "_get_article_detailed_metadata", return_value=metadata
            ):
                results = engine._get_full_content(items)
                snippet = results[0]["snippet"]
                assert "KW1" in snippet
                assert "KW2" not in snippet


class TestGetPreviewsAuthorTruncation:
    def test_truncates_long_author_list(self):
        engine = _make_engine(include_authors_in_context=True)
        long_authors = [f"Author{i} LongLastName{i}" for i in range(20)]
        summaries = [
            {
                "id": "33333",
                "title": "Test",
                "link": "https://pubmed.ncbi.nlm.nih.gov/33333/",
                "pubdate": "2024",
                "authors": long_authors,
                "journal": "J Test",
                "pubtype": [],
                "lang": [],
                "doi": "",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["33333"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "..." in snippet


class TestGetPreviewsCitationDetails:
    def test_includes_citation_details(self):
        engine = _make_engine(include_citation_in_context=True)
        summaries = [
            {
                "id": "44444",
                "title": "Citation Test",
                "link": "https://pubmed.ncbi.nlm.nih.gov/44444/",
                "pubdate": "2024",
                "authors": [],
                "journal": "J Cit",
                "volume": "12",
                "issue": "3",
                "pages": "45-50",
                "pubtype": [],
                "lang": [],
                "doi": "",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["44444"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "Vol 12" in snippet
                        assert "Issue 3" in snippet
                        assert "pp 45-50" in snippet


class TestGetPreviewsLanguageIndicator:
    def test_non_english_language_indicator(self):
        engine = _make_engine(include_language_in_context=True)
        summaries = [
            {
                "id": "55555",
                "title": "German Article",
                "link": "https://pubmed.ncbi.nlm.nih.gov/55555/",
                "pubdate": "2024",
                "authors": [],
                "journal": "J",
                "pubtype": [],
                "lang": ["ger"],
                "doi": "",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["55555"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "[GER]" in snippet


class TestGetPreviewsIdentifiers:
    def test_includes_pmid_and_doi(self):
        engine = _make_engine(
            include_pmid_in_context=True, include_doi_in_context=True
        )
        summaries = [
            {
                "id": "66666",
                "title": "ID Test",
                "link": "https://pubmed.ncbi.nlm.nih.gov/66666/",
                "pubdate": "2024",
                "authors": [],
                "journal": "J",
                "pubtype": [],
                "lang": [],
                "doi": "10.1234/abc",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["66666"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "PMID: 66666" in snippet
                        assert "DOI: 10.1234/abc" in snippet


class TestGetPreviewsSeparatorLogic:
    def test_period_separator_with_authors(self):
        engine = _make_engine(
            include_authors_in_context=True, include_journal_in_context=True
        )
        summaries = [
            {
                "id": "77777",
                "title": "Sep Test",
                "link": "https://pubmed.ncbi.nlm.nih.gov/77777/",
                "pubdate": "2024",
                "authors": ["Smith J"],
                "journal": "Nature",
                "pubtype": [],
                "lang": [],
                "doi": "",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["77777"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "Smith J. Nature" in snippet

    def test_dash_separator_without_authors(self):
        engine = _make_engine(
            include_authors_in_context=False,
            include_journal_in_context=True,
            include_year_in_context=True,
        )
        summaries = [
            {
                "id": "88888",
                "title": "Dash Test",
                "link": "https://pubmed.ncbi.nlm.nih.gov/88888/",
                "pubdate": "2024 Jan",
                "authors": [],
                "journal": "Science",
                "pubtype": [],
                "lang": [],
                "doi": "",
            }
        ]
        with patch.object(
            engine, "_optimize_query_for_pubmed", return_value="test"
        ):
            with patch.object(
                engine, "_adaptive_search", return_value=(["88888"], "test")
            ):
                with patch.object(
                    engine, "_get_article_summaries", return_value=summaries
                ):
                    with patch.object(
                        engine, "_get_article_abstracts", return_value={}
                    ):
                        previews = engine._get_previews("test")
                        snippet = previews[0]["snippet"]
                        assert "Science - 2024" in snippet


class TestIsHistoricalFocusedLLMErrorFallback:
    def test_llm_error_falls_back_to_keyword_check(self):
        mock_llm = Mock()
        mock_llm.invoke.side_effect = RuntimeError("LLM down")
        engine = _make_engine(llm=mock_llm)
        assert engine._is_historical_focused("history of penicillin") is True

    def test_llm_error_fallback_returns_false_for_non_historical(self):
        mock_llm = Mock()
        mock_llm.invoke.side_effect = RuntimeError("LLM down")
        engine = _make_engine(llm=mock_llm)
        assert engine._is_historical_focused("new cancer therapies") is False


class TestSearchPubmedAPIKeyInclusion:
    def test_api_key_passed_in_params(self):
        engine = _make_engine(api_key="MY-KEY-123")
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.json.return_value = {
                "esearchresult": {"idlist": [], "count": "0"}
            }
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            engine._search_pubmed("test query")
            call_kwargs = mock_get.call_args[1]
            assert call_kwargs["params"]["api_key"] == "MY-KEY-123"


class TestSearchPubmedDaysLimitParams:
    def test_days_limit_sets_reldate_and_datetype(self):
        engine = _make_engine(days_limit=30)
        with patch(f"{MODULE}.safe_get") as mock_get:
            mock_resp = Mock()
            mock_resp.json.return_value = {
                "esearchresult": {"idlist": ["1"], "count": "1"}
            }
            mock_resp.raise_for_status = Mock()
            mock_get.return_value = mock_resp
            engine._search_pubmed("test query")
            call_kwargs = mock_get.call_args[1]
            assert call_kwargs["params"]["reldate"] == 30
            assert call_kwargs["params"]["datetype"] == "pdat"


class TestCreateEnrichedContentAffiliations:
    def test_single_affiliation(self):
        engine = _make_engine()
        result_dict = {"affiliations": ["Harvard Medical School"]}
        content = engine._create_enriched_content(result_dict, "Abstract text.")
        assert "Institution: Harvard Medical School" in content

    def test_multiple_affiliations(self):
        engine = _make_engine()
        result_dict = {
            "affiliations": ["Harvard Medical School", "MIT", "Stanford"]
        }
        content = engine._create_enriched_content(result_dict, "Abstract text.")
        assert "Institutions:" in content
        assert "Harvard Medical School" in content
        assert "MIT" in content
        assert "Stanford" in content


class TestCreateEnrichedContentGrants:
    def test_single_grant_with_agency_and_id(self):
        engine = _make_engine()
        result_dict = {"grants": [{"agency": "NIH", "id": "R01-12345"}]}
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Funded by: NIH (Grant ID: R01-12345)" in content

    def test_multiple_grants(self):
        engine = _make_engine()
        result_dict = {
            "grants": [
                {"agency": "NIH", "id": "R01"},
                {"agency": "NSF", "id": "G02"},
            ]
        }
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Funding Sources:" in content
        assert "NIH" in content
        assert "NSF" in content


class TestCreateEnrichedContentCOIFiltering:
    def test_trivial_no_conflict_filtered(self):
        engine = _make_engine()
        result_dict = {
            "conflict_of_interest": "The authors declare no competing interests."
        }
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Conflict of Interest" not in content

    def test_no_conflict_with_but_included(self):
        engine = _make_engine()
        result_dict = {
            "conflict_of_interest": "The authors declare no competing interests, but Dr. X received consulting fees."
        }
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Conflict of Interest" in content
        assert "consulting fees" in content

    def test_real_conflict_included(self):
        engine = _make_engine()
        result_dict = {
            "conflict_of_interest": "Dr. Smith is on the advisory board of Pharma Corp."
        }
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Conflict of Interest" in content
        assert "advisory board" in content


class TestCreateEnrichedContentKeywordsFooter:
    def test_keywords_in_footer(self):
        engine = _make_engine()
        result_dict = {"keywords": ["CRISPR", "gene therapy", "oncology"]}
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Keywords: CRISPR, gene therapy, oncology" in content

    def test_mesh_terms_in_footer(self):
        engine = _make_engine()
        result_dict = {"mesh_terms": ["Neoplasms", "Therapeutics"]}
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "Medical Topics (MeSH): Neoplasms, Therapeutics" in content


class TestCreateEnrichedContentStudyType:
    def test_significant_pub_type_added(self):
        engine = _make_engine()
        result_dict = {
            "publication_types": ["Meta-Analysis", "Journal Article"]
        }
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "[Study Type: Meta-Analysis]" in content

    def test_non_significant_pub_type_omitted(self):
        engine = _make_engine()
        result_dict = {"publication_types": ["Journal Article"]}
        content = engine._create_enriched_content(result_dict, "Abstract.")
        assert "[Study Type:" not in content
