"""Integration tests for app.api.v1.public — public feed, literature detail, tags, journals."""

import uuid
import datetime

import pytest
import pytest_asyncio

from app.models.literature import GlobalLiterature, GlobalTag, GlobalLiteratureTag, GlobalJournal

PMID = 90000940
PMID2 = 90000941
ISSN = "1234-5678"


@pytest_asyncio.fixture
async def seed_literature(db):
    """Seed literature, tags, and journal for public tests. Cleans up first."""
    from sqlalchemy import delete as sql_delete
    await db.execute(sql_delete(GlobalLiteratureTag))
    await db.execute(sql_delete(GlobalLiterature).where(GlobalLiterature.pmid.in_([PMID, PMID2])))
    await db.execute(sql_delete(GlobalTag).where(GlobalTag.path == "cancer::test"))
    await db.execute(sql_delete(GlobalJournal).where(GlobalJournal.issn == ISSN))

    tag = GlobalTag(
        id=uuid.uuid4(), name_zh="测试癌种", name_en="Test Cancer",
        path="cancer::test", tag_category="cancer", level=2,
    )
    db.add(tag)
    await db.flush()

    lit1 = GlobalLiterature(
        id=uuid.uuid4(), pmid=PMID, title="Public Feed Article One",
        abstract="Abstract for article one", authors=[{"family": "Zhao", "affiliation": "Hospital A"}],
        journal="Nature Medicine", journal_issn=ISSN, doi="10.1000/nm.pub1",
        pub_date=datetime.date(2024, 7, 1), pub_year=2024,
        ai_summary={"one_liner": "Key finding about cancer"},
        study_design={"type": "RCT", "phase": "III"},
        trial_reg={"id": "NCT123456", "source": "ClinicalTrials.gov"},
    )
    lit2 = GlobalLiterature(
        id=uuid.uuid4(), pmid=PMID2, title="Public Feed Article Two",
        abstract="Abstract for article two", authors=[{"family": "Qian"}],
        journal="The Lancet", doi="10.1000/lan.pub2",
        pub_date=datetime.date(2024, 6, 15), pub_year=2024,
    )
    db.add_all([lit1, lit2])
    await db.flush()

    glt = GlobalLiteratureTag(literature_id=lit1.id, tag_id=tag.id, is_major=True)
    db.add(glt)

    journal = GlobalJournal(
        id=uuid.uuid4(), name="Nature Medicine", issn=ISSN,
        tier="1", impact_factor=50.0,
    )
    db.add(journal)
    await db.commit()
    return {"tag": tag, "literatures": [lit1, lit2], "journal": journal}


# ── Public Feed ──


@pytest.mark.asyncio
async def test_public_feed_structure(client):
    """Public feed returns valid structure."""
    r = await client.get("/api/v1/public/feed")
    assert r.status_code == 200
    data = r.json()
    assert "items" in data
    assert "total" in data
    assert isinstance(data["items"], list)
    assert isinstance(data["total"], int)


@pytest.mark.asyncio
async def test_public_feed_with_data(client, seed_literature):
    """Public feed returns seeded literature."""
    r = await client.get("/api/v1/public/feed")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 2
    titles = {it["title"] for it in data["items"]}
    assert "Public Feed Article One" in titles
    assert "Public Feed Article Two" in titles


@pytest.mark.asyncio
async def test_public_feed_tag_filter_uuid(client, seed_literature):
    """Public feed filters by tag UUID."""
    tag_id = str(seed_literature["tag"].id)
    r = await client.get(f"/api/v1/public/feed?tag={tag_id}")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 1
    assert data["items"][0]["pmid"] == PMID


@pytest.mark.asyncio
async def test_public_feed_tag_filter_name(client, seed_literature):
    """Public feed filters by tag Chinese name."""
    r = await client.get("/api/v1/public/feed?tag=测试癌种")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 1
    assert data["items"][0]["pmid"] == PMID


@pytest.mark.asyncio
async def test_public_feed_pagination(client, seed_literature):
    """Public feed pagination returns correct subset."""
    r = await client.get("/api/v1/public/feed?page=1&page_size=1")
    assert r.status_code == 200
    data = r.json()
    assert len(data["items"]) == 1
    assert data["total"] >= 2


@pytest.mark.asyncio
async def test_public_feed_ai_summary_included(client, seed_literature):
    """Public feed includes ai_summary."""
    r = await client.get("/api/v1/public/feed")
    assert r.status_code == 200
    data = r.json()
    with_summary = [it for it in data["items"] if it.get("ai_summary")]
    assert len(with_summary) >= 1
    assert "Key finding about cancer" in with_summary[0]["ai_summary"]


@pytest.mark.asyncio
async def test_public_feed_strips_sensitive_cards_fields(client, seed_literature):
    """Public feed cards should not expose study_design or trial_reg."""
    r = await client.get("/api/v1/public/feed")
    assert r.status_code == 200
    data = r.json()
    for item in data["items"]:
        assert "study_design" not in item, f"study_design leaked in card {item['pmid']}"
        assert "trial_reg" not in item, f"trial_reg leaked in card {item['pmid']}"


# ── Literature Detail ──


@pytest.mark.asyncio
async def test_public_literature_detail(client, seed_literature):
    """Public literature detail returns basic info without sensitive fields."""
    r = await client.get(f"/api/v1/public/literature/{PMID}")
    assert r.status_code == 200
    data = r.json()
    assert data["pmid"] == PMID
    assert data["title"] == "Public Feed Article One"
    assert data["abstract"] == "Abstract for article one"
    assert data["doi"] == "10.1000/nm.pub1"
    assert data["journal"] == "Nature Medicine"
    assert "Zhao" in str(data["authors"])
    assert len(data["tags"]) == 1
    assert data["tags"][0]["name_zh"] == "测试癌种"
    # 公开详情不应返回敏感/增值字段（但 PubMed 原始元数据如 chemical_list 等可以）
    assert "full_text_sections" not in data
    assert "pre_extracted_data" not in data
    assert "pico" not in data
    assert "ai_summary" not in data
    assert "study_design" not in data
    assert "trial_reg" not in data
    # PubMed 原始元数据应存在
    assert "grants" in data
    assert "chemical_list" in data
    assert "gene_symbols" in data
    assert "databank_list" in data
    assert "suppl_mesh_list" in data


@pytest.mark.asyncio
async def test_public_literature_detail_not_found(client):
    """Public detail returns 404 for non-existent PMID."""
    r = await client.get("/api/v1/public/literature/99999999")
    assert r.status_code == 404
    assert "not found" in r.json()["error"]["message"].lower()


# ── Tags ──


@pytest.mark.asyncio
async def test_public_tags(client):
    """Public tags returns categories even when no tags exist."""
    r = await client.get("/api/v1/public/tags")
    assert r.status_code == 200
    data = r.json()
    assert "tags" in data
    assert "categories" in data
    categories = {c["key"] for c in data["categories"]}
    assert "cancer" in categories
    assert "gene" in categories


@pytest.mark.asyncio
async def test_public_tags_with_data(client, seed_literature):
    """Public tags returns seeded tags."""
    r = await client.get("/api/v1/public/tags")
    assert r.status_code == 200
    data = r.json()
    tag_names = {t["name_zh"] for t in data["tags"]}
    assert "测试癌种" in tag_names


@pytest.mark.asyncio
async def test_public_tags_authenticated(client, auth_headers):
    """Public tags also work for authenticated users."""
    r = await client.get("/api/v1/public/tags", headers=auth_headers)
    assert r.status_code == 200


# ── Journals ──


@pytest.mark.asyncio
async def test_public_journals_empty(client):
    """Public journals returns empty list."""
    r = await client.get("/api/v1/public/journals")
    assert r.status_code == 200
    data = r.json()
    assert "journals" in data
    assert "total" in data


@pytest.mark.asyncio
async def test_public_journals_with_data(client, seed_literature):
    """Public journals returns journal with article count."""
    r = await client.get("/api/v1/public/journals")
    assert r.status_code == 200
    data = r.json()
    journals = {j["issn"]: j for j in data["journals"]}
    assert ISSN in journals
    assert journals[ISSN]["name"] == "Nature Medicine"
    assert journals[ISSN]["article_count"] >= 1


@pytest.mark.asyncio
async def test_journal_articles(client, seed_literature):
    """Journal articles lists articles for a given ISSN."""
    r = await client.get(f"/api/v1/public/journals/{ISSN}")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 1
    assert data["items"][0]["pmid"] == PMID


@pytest.mark.asyncio
async def test_journal_articles_no_results(client):
    """Journal articles returns empty for non-existent ISSN."""
    r = await client.get("/api/v1/public/journals/0000-0000")
    assert r.status_code == 200
    data = r.json()
    assert data["items"] == []
    assert data["total"] == 0


# ── Stats ──


@pytest.mark.asyncio
async def test_public_stats(client):
    """Public stats returns structure with non-negative values."""
    r = await client.get("/api/v1/public/stats")
    assert r.status_code == 200
    data = r.json()
    assert "literature_total" in data
    assert "journal_total" in data
    assert "daily_avg_30d" in data
    assert data["literature_total"] >= 0
    assert data["journal_total"] >= 0
    assert data["daily_avg_30d"] >= 0


# ── Hot Articles ──


@pytest.mark.asyncio
async def test_public_hot_articles_empty(client):
    """Hot-articles returns empty items when no recent literature with citations."""
    r = await client.get("/api/v1/public/hot-articles")
    assert r.status_code == 200
    data = r.json()
    assert "items" in data
    assert isinstance(data["items"], list)


@pytest.mark.asyncio
async def test_public_hot_articles_with_data(client, db, seed_literature):
    """Hot-articles returns seeded literature sorted by cited_by_count."""
    from sqlalchemy import select
    lit1 = (await db.execute(select(GlobalLiterature).where(GlobalLiterature.pmid == PMID))).scalar()
    lit2 = (await db.execute(select(GlobalLiterature).where(GlobalLiterature.pmid == PMID2))).scalar()
    lit1.cited_by_count = 10
    lit2.cited_by_count = 5
    lit1.pub_date = datetime.date.today() - datetime.timedelta(days=30)
    lit2.pub_date = datetime.date.today() - datetime.timedelta(days=60)
    await db.commit()

    r = await client.get("/api/v1/public/hot-articles")
    assert r.status_code == 200
    data = r.json()
    assert len(data["items"]) >= 2
    assert data["items"][0]["cited_by_count"] == 10
    assert data["items"][1]["cited_by_count"] == 5
    assert data["items"][0]["pmid"] == PMID
    assert data["items"][1]["pmid"] == PMID2
