"""端到端测试：PubMed 新增字段解析（ChemicalList, GeneSymbolList 等）

策略：
1. 用硬编码的 XML 片段（截取自真实 PubMed）验证解析逻辑
2. 用真实 E-utilities 数据验证端到端 DB 存储和 API 返回
"""
import os
import sys
from datetime import date

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from app.compat import UTC

# ═══════════════════════════════════════════════════════════════════
# 硬编码测试数据：取自真实的 PubMed XML 响应
# 每段 XML 验证一个字段的解析逻辑
# ═══════════════════════════════════════════════════════════════════

_SAMPLE_CHEMICAL_XML = """<?xml version="1.0"?>
<!DOCTYPE PubmedArticleSet PUBLIC "-//NLM//DTD PubMedArticle, 20101110//EN" "http://www.nlm.nih.gov/databases/dtd/nlmmedlincitation-20101110.dtd">
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>38890137</PMID>
<Article>
<Journal><Title>Test J</Title></Journal>
<ArticleTitle>Test Chemical Article</ArticleTitle>
</Article>
<ChemicalList>
<Chemical>
<RegistryNumber>N762921K75</RegistryNumber>
<NameOfSubstance UI="D009584">Nitrogen</NameOfSubstance>
</Chemical>
<Chemical>
<RegistryNumber>0NOK9I7LFM</RegistryNumber>
<NameOfSubstance UI="C506518">tislelizumab</NameOfSubstance>
</Chemical>
</ChemicalList>
</MedlineCitation>
<PubmedData><PublicationStatus>epublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""

_SAMPLE_GENE_XML = """<?xml version="1.0"?>
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>15592482</PMID>
<Article>
<Journal><Title>Test J</Title></Journal>
<ArticleTitle>Test Gene Article</ArticleTitle>
<GeneSymbolList>
<GeneSymbol>TP53</GeneSymbol>
<GeneSymbol>EGFR</GeneSymbol>
<GeneSymbol>BRAF</GeneSymbol>
</GeneSymbolList>
</Article>
</MedlineCitation>
<PubmedData><PublicationStatus>ppublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""

_SAMPLE_DATABANK_XML = """<?xml version="1.0"?>
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>25831159</PMID>
<Article>
<Journal><Title>Test J</Title></Journal>
<ArticleTitle>Test Databank Article</ArticleTitle>
</Article>
<DataBankList>
<DataBank>
<DataBankName>GEO</DataBankName>
<AccessionNumberList>
<AccessionNumber>GSE12345</AccessionNumber>
<AccessionNumber>GSE67890</AccessionNumber>
</AccessionNumberList>
</DataBank>
<DataBank>
<DataBankName>ClinicalTrials.gov</DataBankName>
<AccessionNumberList>
<AccessionNumber>NCT01644539</AccessionNumber>
</AccessionNumberList>
</DataBank>
</DataBankList>
</MedlineCitation>
<PubmedData><PublicationStatus>epublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""

_SAMPLE_SUPPLMESH_XML = """<?xml version="1.0"?>
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>38537958</PMID>
<Article>
<Journal><Title>Test J</Title></Journal>
<ArticleTitle>Test SupplMesh Article</ArticleTitle>
</Article>
<SupplMeshList>
<SupplMeshName Type="Disease" UI="C537751">Oncogenic osteomalacia</SupplMeshName>
<SupplMeshName Type="Pharmacological" UI="C000656962">Nivolumab</SupplMeshName>
</SupplMeshList>
</MedlineCitation>
<PubmedData><PublicationStatus>ppublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""

_SAMPLE_NUMREFS_XML = """<?xml version="1.0"?>
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>34906478</PMID>
<Article>
<Journal><Title>Test J</Title></Journal>
<ArticleTitle>Test Refs Article</ArticleTitle>
</Article>
<NumberOfReferences>46</NumberOfReferences>
</MedlineCitation>
<PubmedData><PublicationStatus>epublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""


class TestParsingLogic:
    """用硬编码 XML 验证各字段的解析逻辑"""

    def _parse(self, xml_text: str):
        from app.services.pubmed_api import _parse_pubmed_xml
        return _parse_pubmed_xml(xml_text)

    def test_chemical_list(self):
        arts = self._parse(_SAMPLE_CHEMICAL_XML)
        assert len(arts) == 1
        cl = arts[0]["chemical_list"]
        assert len(cl) == 2
        assert cl[0] == {"name": "Nitrogen", "registry_number": "N762921K75", "mesh_ui": "D009584"}
        assert cl[1]["name"] == "tislelizumab"

    def test_gene_symbols(self):
        arts = self._parse(_SAMPLE_GENE_XML)
        assert len(arts) == 1
        gs = arts[0]["gene_symbols"]
        assert gs == ["TP53", "EGFR", "BRAF"]

    def test_databank_list(self):
        arts = self._parse(_SAMPLE_DATABANK_XML)
        assert len(arts) == 1
        db = arts[0]["databank_list"]
        assert len(db) == 2
        assert db[0]["databank_name"] == "GEO"
        assert db[0]["accession_numbers"] == ["GSE12345", "GSE67890"]
        assert db[1]["databank_name"] == "ClinicalTrials.gov"
        assert db[1]["accession_numbers"] == ["NCT01644539"]

    def test_suppl_mesh(self):
        arts = self._parse(_SAMPLE_SUPPLMESH_XML)
        assert len(arts) == 1
        sm = arts[0]["suppl_mesh_list"]
        assert len(sm) == 2
        assert sm[0] == {"descriptor": "Oncogenic osteomalacia", "ui": "C537751", "type": "Disease"}
        assert sm[1]["type"] == "Pharmacological"

    def test_num_refs(self):
        arts = self._parse(_SAMPLE_NUMREFS_XML)
        assert len(arts) == 1
        assert arts[0]["num_refs"] == 46

    def test_publication_status(self):
        arts = self._parse(_SAMPLE_CHEMICAL_XML)
        assert arts[0]["publication_status"] == "epublish"

    def test_article_date(self):
        arts = self._parse(_SAMPLE_CHEMICAL_XML)
        assert arts[0]["article_date"] is None  # No ArticleDate in this XML

    def test_all_fields_have_defaults(self):
        """即使 XML 中没有这些字段，返回的 dict 也必须有默认值"""
        from app.services.pubmed_api import _parse_pubmed_xml

        xml = """<?xml version="1.0"?>
<PubmedArticleSet>
<PubmedArticle>
<MedlineCitation Owner="NLM" Status="publisher">
<PMID>99999999</PMID>
<Article>
<Journal><Title>J</Title></Journal>
<ArticleTitle>Minimal Article</ArticleTitle>
</Article>
</MedlineCitation>
<PubmedData><PublicationStatus>epublish</PublicationStatus></PubmedData>
</PubmedArticle>
</PubmedArticleSet>"""
        arts = _parse_pubmed_xml(xml)
        a = arts[0]
        assert a["chemical_list"] == []
        assert a["gene_symbols"] == []
        assert a["num_refs"] is None
        assert a["publication_status"] == "epublish"
        assert a["article_date"] is None
        assert a["databank_list"] == []
        assert a["suppl_mesh_list"] == []

    def test_europe_pmc_translation(self):
        """Europe PMC JSON 到标准格式的转换也包含新字段"""
        from app.services.pubmed_api import _parse_europe_pmc_article

        art_json = {
            "id": "38890137",
            "title": "Test",
            "chemicalList": [
                {"name": "Cisplatin", "registryNumber": "Q20Q21Q62J", "ui": "D002945"}
            ],
            "geneSymbolList": ["EGFR", "KRAS"],
            "numReferences": 42,
            "publicationStatus": "epublish",
            "dataBankList": [
                {"dataBankName": "GEO", "accessionNumberList": ["GSE12345"]}
            ],
            "supplMeshList": [
                {"supplMeshName": "Test", "supplMeshUI": "C123456", "supplMeshType": "Disease"}
            ],
        }
        result = _parse_europe_pmc_article(art_json)
        assert result["chemical_list"] == [{"name": "Cisplatin", "registry_number": "Q20Q21Q62J", "mesh_ui": "D002945"}]
        assert result["gene_symbols"] == ["EGFR", "KRAS"]
        assert result["num_refs"] == 42
        assert result["publication_status"] == "epublish"
        assert result["databank_list"] == [{"databank_name": "GEO", "accession_numbers": ["GSE12345"]}]
        assert result["suppl_mesh_list"] == [{"descriptor": "Test", "ui": "C123456", "type": "Disease"}]


# ═══════════════════════════════════════════════════════════════════
# 真实数据端到端测试（需要 PostgreSQL + live server）
# ═══════════════════════════════════════════════════════════════════


@pytest.mark.asyncio
async def test_enrichment_fields_stored_in_db(db):
    """验证新字段正确存储到数据库"""
    from app.models.literature import GlobalLiterature

    lit = GlobalLiterature(
        pmid=99999001,
        title="Test Storage Article",
        chemical_list=[{"name": "Cisplatin", "registry_number": "Q20Q21Q62J", "mesh_ui": "D002945"}],
        gene_symbols=["TP53", "EGFR"],
        num_refs=42,
        publication_status="epublish",
        article_date=date(2024, 6, 15),
        databank_list=[{"databank_name": "GEO", "accession_numbers": ["GSE12345"]}],
        suppl_mesh_list=[{"descriptor": "Test", "ui": "C123456", "type": "Disease"}],
    )
    db.add(lit)
    await db.flush()

    saved = await db.get(GlobalLiterature, lit.id)
    assert saved is not None
    assert saved.chemical_list == lit.chemical_list
    assert saved.gene_symbols == lit.gene_symbols
    assert saved.num_refs == 42
    assert saved.publication_status == "epublish"
    assert saved.article_date == date(2024, 6, 15)
    assert saved.databank_list == lit.databank_list
    assert saved.suppl_mesh_list == lit.suppl_mesh_list

    await db.delete(saved)
    await db.flush()


@pytest.mark.asyncio
async def test_enrichment_fields_api(client, db):
    """验证 API 返回中包含了新字段"""
    from app.models.literature import GlobalLiterature

    lit = GlobalLiterature(
        pmid=99999002,
        title="Test API Article",
        chemical_list=[{"name": "Nitrogen", "registry_number": "N762921K75", "mesh_ui": "D009584"}],
        gene_symbols=["BRAF"],
        num_refs=10,
        publication_status="ppublish",
        article_date=date(2023, 1, 1),
        databank_list=[{"databank_name": "SRA", "accession_numbers": ["SRP123456"]}],
        suppl_mesh_list=[{"descriptor": "Nivolumab", "ui": "C000656962", "type": "Pharmacological"}],
    )
    db.add(lit)
    await db.commit()

    # 公开 API
    resp = await client.get(f"/api/v1/public/literature/{lit.pmid}")
    assert resp.status_code == 200
    data = resp.json()
    assert "chemical_list" in data
    assert data["chemical_list"] == lit.chemical_list
    assert data["gene_symbols"] == ["BRAF"]
    assert data["num_refs"] == 10
    assert data["publication_status"] == "ppublish"
    assert data["article_date"] == "2023-01-01"
    assert data["databank_list"] == lit.databank_list
    assert data["suppl_mesh_list"] == lit.suppl_mesh_list

    await db.delete(lit)
    await db.commit()


@pytest.mark.asyncio
async def test_none_fields_default_to_empty(client, db):
    """验证没有新字段数据时 API 返回空列表而非 null"""
    from app.models.literature import GlobalLiterature

    lit = GlobalLiterature(pmid=99999003, title="Test Empty Fields")
    db.add(lit)
    await db.commit()

    resp = await client.get(f"/api/v1/public/literature/{lit.pmid}")
    assert resp.status_code == 200
    data = resp.json()
    assert data["chemical_list"] == []
    assert data["gene_symbols"] == []
    assert data["num_refs"] is None
    assert data["publication_status"] is None
    assert data["article_date"] is None
    assert data["databank_list"] == []
    assert data["suppl_mesh_list"] == []

    await db.delete(lit)
    await db.commit()


@pytest.mark.asyncio
async def test_authenticated_api_returns_fields(client, auth_headers, db):
    """登录用户访问文献详情时也有新字段"""
    from app.models.literature import GlobalLiterature

    lit = GlobalLiterature(
        pmid=99999004,
        title="Test Auth API",
        chemical_list=[{"name": "Test", "registry_number": "0", "mesh_ui": "D000000"}],
        gene_symbols=["TP53"],
    )
    db.add(lit)
    await db.commit()

    resp = await client.get(f"/api/v1/literature/{lit.pmid}", headers=auth_headers)
    assert resp.status_code == 200
    data = resp.json()
    assert data["chemical_list"] == lit.chemical_list
    assert data["gene_symbols"] == ["TP53"]

    await db.delete(lit)
    await db.commit()
