"""Tests for app.tasks.pubmed_pipeline"""

import hashlib
from datetime import date

import pytest
from lxml import etree

from app.tasks.pubmed_pipeline import (
    _month_day,
    _month_num,
    extract_article,
    upsert_article,
)


# ─── _month_num ────────────────────────────────────────────────────────────


# ─── _month_num ────────────────────────────────────────────────────────────


class TestMonthNum:
    def test_month_num_numeric(self):
        """"5" returns 5, "12" returns 12"""
        assert _month_num("5") == 5
        assert _month_num("12") == 12

    def test_month_num_string(self):
        """"Jan" returns 1, "Dec" returns 12, "foo" returns 1"""
        assert _month_num("Jan") == 1
        assert _month_num("Dec") == 12
        assert _month_num("foo") == 1


# ─── _month_day ────────────────────────────────────────────────────────────


class TestMonthDay:
    def test_month_day_normal(self):
        """_month_day(2024, 3, 15) returns 15"""
        assert _month_day(2024, 3, "15") == 15

    def test_month_day_feb_leap(self):
        """_month_day(2024, 2, 29) returns 29 (2024 is leap)"""
        assert _month_day(2024, 2, "29") == 29

    def test_month_day_feb_non_leap(self):
        """_month_day(2023, 2, 29) returns 28"""
        assert _month_day(2023, 2, "29") == 28

    def test_month_day_clamp(self):
        """_month_day(2024, 4, 31) returns 30 (April has 30 days)"""
        assert _month_day(2024, 4, "31") == 30

    def test_month_day_invalid(self):
        """_month_day(2024, 1, "abc") returns 1"""
        assert _month_day(2024, 1, "abc") == 1


# ─── extract_article ───────────────────────────────────────────────────────


FULL_XML = """\
<PubmedArticle>
  <MedlineCitation>
    <PMID>12345678</PMID>
    <Article>
      <ArticleTitle>Test Article Title</ArticleTitle>
      <Abstract><AbstractText>Test abstract content</AbstractText></Abstract>
      <AuthorList>
        <Author><LastName>Smith</LastName><ForeName>John</ForeName><AffiliationInfo><Affiliation>Test Univ</Affiliation></AffiliationInfo></Author>
      </AuthorList>
      <ELocationID EIdType="doi">10.1000/test</ELocationID>
      <Journal><Title>Test Journal</Title><ISSN>1234-5678</ISSN><JournalIssue><Volume>10</Volume><Issue>2</Issue><PubDate><Year>2024</Year><Month>Mar</Month><Day>15</Day></PubDate></JournalIssue></Journal>
      <Pagination><MedlinePgn>100-110</MedlinePgn></Pagination>
      <PublicationTypeList><PublicationType>Journal Article</PublicationType><PublicationType>Review</PublicationType></PublicationTypeList>
      <Language>en</Language>
    </Article>
    <MeshHeadingList>
      <MeshHeading><DescriptorName UI="D000001" MajorTopicYN="Y">Neoplasms</DescriptorName></MeshHeading>
    </MeshHeadingList>
  </MedlineCitation>
</PubmedArticle>
"""


class TestExtractArticle:
    def test_extract_article_full(self):
        """Parse a full PubmedArticle XML and verify all fields"""
        elem = etree.fromstring(FULL_XML)
        result = extract_article(elem)

        assert result is not None
        assert result["pmid"] == 12345678
        assert result["title"] == "Test Article Title"
        assert result["abstract"] == "Test abstract content"
        assert len(result["authors"]) == 1
        assert result["authors"][0]["family"] == "Smith"
        assert result["doi"] == "10.1000/test"
        assert result["journal"] == "Test Journal"
        assert result["volume"] == "10"
        assert result["pub_year"] == 2024
        assert "Review" in result["pub_types"]
        assert len(result["mesh_headings"]) == 1
        assert result["mesh_headings"][0]["descriptor"] == "Neoplasms"
        assert result["language"] == "en"
        # raw_xml_hash should be a 64-char hex string
        assert isinstance(result["raw_xml_hash"], str)
        assert len(result["raw_xml_hash"]) == 64
        assert all(c in "0123456789abcdef" for c in result["raw_xml_hash"])

    def test_extract_article_no_medline(self):
        """Element without MedlineCitation returns None"""
        elem = etree.fromstring("<PubmedArticle><Other/></PubmedArticle>")
        assert extract_article(elem) is None

    def test_extract_article_no_pmid(self):
        """Element without PMID returns None"""
        xml = """\
<PubmedArticle>
  <MedlineCitation>
    <Article>
      <ArticleTitle>Test</ArticleTitle>
    </Article>
  </MedlineCitation>
</PubmedArticle>"""
        elem = etree.fromstring(xml)
        assert extract_article(elem) is None


# ─── upsert_article ────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_upsert_article_new(db):
    """Insert a new article and verify it exists in the database"""
    from app.models.literature import GlobalLiterature
    from sqlalchemy import select

    article_data = {
        "pmid": 99999999,
        "title": "Test Upsert Article",
        "abstract": "This is a test article for upsert.",
        "authors": [{"family": "Smith", "given": "John", "affiliation": "Test Univ"}],
        "doi": "10.1000/test-upsert",
        "journal": "Test Journal",
        "journal_issn": "1234-5678",
        "volume": "10",
        "issue": "2",
        "pages": "100-110",
        "pub_date": date(2024, 3, 15),
        "pub_year": 2024,
        "pub_types": ["Journal Article"],
        "mesh_headings": [{"descriptor": "Neoplasms", "ui": "D000001", "major": True, "qualifiers": []}],
        "language": "en",
        "raw_xml_hash": "a" * 64,
    }

    result = await upsert_article(article_data, db=db)
    assert result is True

    # Verify the article exists in the database
    stmt = select(GlobalLiterature).where(GlobalLiterature.pmid == 99999999)
    obj = (await db.execute(stmt)).scalar()
    assert obj is not None
    assert obj.title == "Test Upsert Article"
    assert obj.doi == "10.1000/test-upsert"
    # cleanup is handled by db fixture (rollback on exception, commit on success)
    # per-module truncation handles cross-module cleanup
