"""PubMed API service tests"""

import uuid
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

# ---------------------------------------------------------------------------
# _fetch_with_retry
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_fetch_with_retry_success():
    """First attempt succeeds -> returns response"""
    mock_response = MagicMock(spec=httpx.Response)
    mock_response.status_code = 200

    mock_client = AsyncMock(spec=httpx.AsyncClient)
    mock_client.get.return_value = mock_response

    from app.services.pubmed_api import _fetch_with_retry

    result = await _fetch_with_retry(mock_client, "http://example.com", {"key": "val"})
    assert result == mock_response
    assert mock_client.get.call_count == 1


@pytest.mark.asyncio
async def test_fetch_with_retry_retry_then_succeed():
    """429 -> retry -> 200 OK"""
    fail_response = MagicMock(spec=httpx.Response)
    fail_response.status_code = 429
    fail_response.raise_for_status.side_effect = httpx.HTTPStatusError(
        "Too Many", request=MagicMock(), response=fail_response
    )

    success_response = MagicMock(spec=httpx.Response)
    success_response.status_code = 200

    mock_client = AsyncMock(spec=httpx.AsyncClient)
    mock_client.get.side_effect = [fail_response, success_response]

    from app.services.pubmed_api import _fetch_with_retry

    result = await _fetch_with_retry(mock_client, "http://example.com", {})
    assert result == success_response
    assert mock_client.get.call_count == 2


@pytest.mark.asyncio
async def test_fetch_with_retry_exhausted():
    """Retryable status on all attempts -> raises on last attempt"""
    response = MagicMock(spec=httpx.Response)
    response.status_code = 503
    response.raise_for_status.side_effect = httpx.HTTPStatusError(
        "Service Unavailable", request=MagicMock(), response=response
    )

    mock_client = AsyncMock(spec=httpx.AsyncClient)
    mock_client.get.return_value = response

    from app.services.pubmed_api import _fetch_with_retry

    with pytest.raises(httpx.HTTPStatusError):
        await _fetch_with_retry(mock_client, "http://example.com", {})
    # MAX_RETRIES=3, all 3 should be attempted before raising
    assert mock_client.get.call_count == 3


@pytest.mark.asyncio
async def test_fetch_with_retry_non_retryable():
    """Non-retryable status (400) -> raises immediately, no retry"""
    response = MagicMock(spec=httpx.Response)
    response.status_code = 400
    response.raise_for_status.side_effect = httpx.HTTPStatusError(
        "Bad Request", request=MagicMock(), response=response
    )

    mock_client = AsyncMock(spec=httpx.AsyncClient)
    mock_client.get.return_value = response

    from app.services.pubmed_api import _fetch_with_retry

    with pytest.raises(httpx.HTTPStatusError):
        await _fetch_with_retry(mock_client, "http://example.com", {})
    assert mock_client.get.call_count == 1


@pytest.mark.asyncio
async def test_fetch_with_retry_timeout_raises():
    """TimeoutException on all attempts -> raises"""
    mock_client = AsyncMock(spec=httpx.AsyncClient)
    mock_client.get.side_effect = httpx.TimeoutException("timed out")

    from app.services.pubmed_api import _fetch_with_retry

    with pytest.raises(httpx.TimeoutException):
        await _fetch_with_retry(mock_client, "http://example.com", {})
    assert mock_client.get.call_count == 3


# ---------------------------------------------------------------------------
# search_pmids
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_search_pmids_success():
    """Valid JSON response returns PMID integer list"""
    mock_response = MagicMock(spec=httpx.Response)
    mock_response.json.return_value = {"esearchresult": {"idlist": ["12345678", "23456789"]}}

    from app.services.pubmed_api import search_pmids

    with patch("app.services.pubmed_api._fetch_with_retry", return_value=mock_response):
        result = await search_pmids("cancer", 10)

    assert result == [12345678, 23456789]


@pytest.mark.asyncio
async def test_search_pmids_empty():
    """Empty idlist returns empty list"""
    mock_response = MagicMock(spec=httpx.Response)
    mock_response.json.return_value = {"esearchresult": {"idlist": []}}

    from app.services.pubmed_api import search_pmids

    with patch("app.services.pubmed_api._fetch_with_retry", return_value=mock_response):
        result = await search_pmids("cancer", 10)

    assert result == []


@pytest.mark.asyncio
async def test_search_pmids_missing_key():
    """Missing esearchresult key returns empty list"""
    mock_response = MagicMock(spec=httpx.Response)
    mock_response.json.return_value = {}

    from app.services.pubmed_api import search_pmids

    with patch("app.services.pubmed_api._fetch_with_retry", return_value=mock_response):
        result = await search_pmids("cancer", 10)

    assert result == []


# ---------------------------------------------------------------------------
# _tag_article
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_tag_article_no_mesh():
    """Empty mesh_headings -> returns 0"""
    db = AsyncMock()

    from app.services.pubmed_api import _tag_article

    result = await _tag_article(db, uuid.uuid4(), [])
    assert result == 0


@pytest.mark.asyncio
async def test_tag_article_no_mesh_ui():
    """Mesh headings without UI values -> returns 0"""
    db = AsyncMock()

    from app.services.pubmed_api import _tag_article

    result = await _tag_article(
        db, uuid.uuid4(),
        [{"descriptor": "Cancer", "ui": "", "major": False}],
    )
    assert result == 0


@pytest.mark.asyncio
async def test_tag_article_matches_tags():
    """Matching mesh_ui creates GlobalLiteratureTag entries"""
    db = AsyncMock()
    lit_id = uuid.uuid4()
    tag_id = uuid.uuid4()

    mock_tag = MagicMock()
    mock_tag.id = tag_id
    mock_tag.mesh_ui = "D000001"

    mock_match = MagicMock()
    mock_match.scalars.return_value.all.return_value = [mock_tag]
    mock_empty = MagicMock()
    mock_empty.scalar.return_value = None
    db.execute.side_effect = [mock_match, mock_empty]

    mesh_headings = [{"descriptor": "Cancer", "ui": "D000001", "major": True}]

    from app.services.pubmed_api import _tag_article

    result = await _tag_article(db, lit_id, mesh_headings)
    assert result == 1
    db.add.assert_called_once()


@pytest.mark.asyncio
async def test_tag_article_no_match():
    """mesh_ui does not match any global tag -> returns 0"""
    db = AsyncMock()
    lit_id = uuid.uuid4()

    mock_result = MagicMock()
    mock_result.scalars.return_value.all.return_value = []
    db.execute.return_value = mock_result

    mesh_headings = [{"descriptor": "RareDisease", "ui": "D999999", "major": False}]

    from app.services.pubmed_api import _tag_article

    result = await _tag_article(db, lit_id, mesh_headings)
    assert result == 0
    db.add.assert_not_called()


# ---------------------------------------------------------------------------
# fetch_articles
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_fetch_articles_empty():
    """Empty pmid list -> returns []"""
    from app.services.pubmed_api import fetch_articles

    result = await fetch_articles([])
    assert result == []


@pytest.mark.asyncio
async def test_fetch_articles_returns_parsed():
    """Valid pmids returns parsed article dicts"""
    pmids = [12345678]
    mock_xml = """<?xml version="1.0"?>
<!DOCTYPE PubmedArticleSet PUBLIC "-//NLM//DTD PubmedArticleSet 2019//EN"
 "https://dtd.nlm.nih.gov/ncbi/pubmed/out/pubmed_190101.dtd">
<PubmedArticleSet>
  <PubmedArticle>
    <MedlineCitation Status="Publisher">
      <PMID>12345678</PMID>
      <Article>
        <ArticleTitle>Test Article</ArticleTitle>
        <Language>en</Language>
      </Article>
    </MedlineCitation>
  </PubmedArticle>
</PubmedArticleSet>"""
    mock_response = MagicMock(spec=httpx.Response)
    mock_response.text = mock_xml

    from app.services.pubmed_api import fetch_articles

    with patch("app.services.pubmed_api._fetch_with_retry", return_value=mock_response):
        result = await fetch_articles(pmids)

    assert len(result) == 1
    assert result[0]["pmid"] == 12345678
    assert result[0]["title"] == "Test Article"


__all__ = []
