"""Integration tests for app.api.v1.approvals — drug approvals & guideline tracking."""

import uuid
import datetime

import pytest
import pytest_asyncio
from sqlalchemy import select

from app.models.business import DrugApproval, GuidelineVersion, GuidelineEvidence
from app.models.literature import GlobalLiterature, GlobalTag


@pytest_asyncio.fixture
async def seed_approvals(db):
    """Seed drug approvals for testing. Cleans up first to avoid cross-test leakage."""
    from sqlalchemy import delete as sql_delete
    await db.execute(sql_delete(DrugApproval))
    aids = []
    for i, (drug, agency) in enumerate([("奥希替尼", "NMPA"), ("Osimertinib", "FDA"), ("Keytruda", "FDA")]):
        a = DrugApproval(
            id=uuid.uuid4(), drug_name=drug,
            generic_name="osimertinib" if "奥希" in drug or "Osi" in drug else "pembrolizumab",
            target="EGFR", indication="NSCLC",
            approval_agency=agency,
            approval_date=datetime.date(2024, 1, 10 + i),
            approval_type="初始批准" if i == 0 else "补充适应症",
        )
        db.add(a)
        aids.append(a)
    await db.commit()
    return aids


@pytest_asyncio.fixture
async def seed_guidelines(db):
    """Seed guideline versions and evidence. Cleans up first to avoid cross-test leakage."""
    from sqlalchemy import delete as sql_delete
    await db.execute(sql_delete(GuidelineEvidence))
    await db.execute(sql_delete(GuidelineVersion))
    await db.execute(sql_delete(GlobalTag).where(GlobalTag.path == "diseases::NSCLC"))
    await db.execute(sql_delete(GlobalLiterature).where(GlobalLiterature.pmid == 90000900))

    tag = GlobalTag(
        id=uuid.uuid4(), name_zh="非小细胞肺癌", name_en="NSCLC",
        path="diseases::NSCLC", tag_category="cancer", level=1,
    )
    db.add(tag)
    await db.flush()

    lit = GlobalLiterature(
        id=uuid.uuid4(), pmid=90000900, title="KEYNOTE-024: Pembrolizumab in NSCLC",
        abstract="Phase 3 trial...", authors=[{"family": "Reck", "affiliation": "Univ"}],
        journal="NEJM", doi="10.1000/nejm.001",
        pub_date=datetime.date(2024, 1, 1), pub_year=2024,
    )
    db.add(lit)
    await db.flush()

    g1 = GuidelineVersion(
        id=uuid.uuid4(), guideline_source="CSCO",
        version="2024.v1", publish_date=datetime.date(2024, 3, 1),
        change_summary_zh="更新免疫治疗推荐",
        cancer_type_tag_id=tag.id,
    )
    g2 = GuidelineVersion(
        id=uuid.uuid4(), guideline_source="NCCN",
        version="2024.v4", publish_date=datetime.date(2024, 2, 15),
        change_summary_zh="EGFR TKI更新",
        cancer_type_tag_id=tag.id,
    )
    db.add_all([g1, g2])
    await db.flush()

    ev = GuidelineEvidence(
        id=uuid.uuid4(), guideline_version_id=g1.id,
        literature_id=lit.id, recommendation="一线使用",
        recommendation_level="I", treatment_line="1L",
    )
    db.add(ev)
    await db.commit()
    return {"tag": tag, "guidelines": [g1, g2], "literature": lit, "evidence": ev}


# ─── Drug Approvals ───


@pytest.mark.asyncio
async def test_list_approvals_empty(client):
    """List approvals returns empty when none seeded."""
    r = await client.get("/api/v1/approvals/drug-approvals")
    assert r.status_code == 200
    data = r.json()
    assert data["items"] == []
    assert data["total"] == 0


@pytest.mark.asyncio
async def test_list_approvals_with_data(client, seed_approvals):
    """List approvals returns seeded items."""
    r = await client.get("/api/v1/approvals/drug-approvals")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 3
    assert len(data["items"]) >= 3
    assert any(a["drug_name"] == "奥希替尼" for a in data["items"])


@pytest.mark.asyncio
async def test_list_approvals_filter_agency(client, seed_approvals):
    """Filter approvals by agency."""
    r = await client.get("/api/v1/approvals/drug-approvals?agency=FDA")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] >= 2
    assert all(a["approval_agency"] == "FDA" for a in data["items"])


@pytest.mark.asyncio
async def test_list_approvals_filter_nmpa(client, seed_approvals):
    """Filter approvals by NMPA."""
    r = await client.get("/api/v1/approvals/drug-approvals?agency=NMPA")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 1
    assert data["items"][0]["approval_agency"] == "NMPA"


@pytest.mark.asyncio
async def test_drug_timeline(client, seed_approvals):
    """Drug timeline by target."""
    r = await client.get("/api/v1/approvals/drug-approvals/timeline/EGFR")
    assert r.status_code == 200
    data = r.json()
    assert data["target"] == "EGFR"
    assert len(data["approvals"]) >= 2


@pytest.mark.asyncio
async def test_drug_timeline_no_results(client):
    """Drug timeline with non-existent target returns empty."""
    r = await client.get("/api/v1/approvals/drug-approvals/timeline/ZZZZ_NOT_FOUND")
    assert r.status_code == 200
    assert r.json()["approvals"] == []


# ─── Guidelines ───


@pytest.mark.asyncio
async def test_list_guidelines_empty(client):
    """List guidelines returns empty when none seeded."""
    r = await client.get("/api/v1/approvals/guidelines")
    assert r.status_code == 200
    assert r.json()["items"] == []
    assert r.json()["total"] == 0


@pytest.mark.asyncio
async def test_list_guidelines_with_data(client, seed_guidelines):
    """List guidelines returns seeded items."""
    r = await client.get("/api/v1/approvals/guidelines")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 2
    sources = {g["source"] for g in data["items"]}
    assert sources == {"CSCO", "NCCN"}


@pytest.mark.asyncio
async def test_list_guidelines_filter_source(client, seed_guidelines):
    """Filter guidelines by source."""
    r = await client.get("/api/v1/approvals/guidelines?source=CSCO")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 1
    assert data["items"][0]["source"] == "CSCO"


@pytest.mark.asyncio
async def test_guideline_changes(client, seed_guidelines):
    """Guideline version diff."""
    r = await client.get("/api/v1/approvals/guidelines/changes/CSCO/2024.v1/2024.v4")
    assert r.status_code == 200
    data = r.json()
    assert data["from"]["version"] == "2024.v1"
    assert "更新免疫治疗推荐" in data["from"]["summary"]
    # CSCO 2024.v4 doesn't exist, so "to" is None
    assert data["to"] is None


@pytest.mark.asyncio
async def test_guideline_quick_index(client, seed_guidelines):
    """Guideline quick index finds treatments."""
    r = await client.get("/api/v1/approvals/guidelines/quick-index?cancer_type=NSCLC")
    assert r.status_code == 200
    data = r.json()
    assert data["cancer_type"] == "非小细胞肺癌"
    assert len(data["standard_treatments"]) >= 1
    assert len(data["guidelines_referenced"]) >= 1


@pytest.mark.asyncio
async def test_guideline_quick_index_no_tag(client):
    """Quick index returns empty when cancer type tag not found."""
    r = await client.get("/api/v1/approvals/guidelines/quick-index?cancer_type=XYZ_UNKNOWN")
    assert r.status_code == 200
    data = r.json()
    assert data["standard_treatments"] == []
    assert "No matching" in data["note"]


@pytest.mark.asyncio
async def test_guideline_evidence_list(client, seed_guidelines):
    """Guideline evidence list."""
    gid = str(seed_guidelines["guidelines"][0].id)
    r = await client.get(f"/api/v1/approvals/guidelines/{gid}/evidence")
    assert r.status_code == 200
    data = r.json()
    assert data["total"] == 1
    assert data["items"][0]["recommendation"] == "一线使用"
    assert data["items"][0]["level"] == "I"
    assert data["items"][0]["pmid"] == 90000900


@pytest.mark.asyncio
async def test_guideline_evidence_invalid_id(client):
    """Invalid guideline ID returns 400."""
    r = await client.get("/api/v1/approvals/guidelines/not-a-uuid/evidence")
    assert r.status_code == 400


@pytest.mark.asyncio
async def test_guideline_evidence_not_found(client):
    """Non-existent guideline ID returns empty."""
    r = await client.get(f"/api/v1/approvals/guidelines/{uuid.uuid4()}/evidence")
    assert r.status_code == 200
    assert r.json()["items"] == []


# ─── Target Subscriptions ───


@pytest_asyncio.fixture
async def auth_ctx(client):
    """Register a user and return (headers, user_id)."""
    email = f"t{uuid.uuid4().hex[:6]}@test.cn"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "TestUser",
    })
    assert resp.status_code == 200
    data = resp.json()
    headers = {"Authorization": f"Bearer {data['token']['access_token']}"}
    user_id = uuid.UUID(data["user"]["id"])
    return headers, user_id


@pytest.mark.asyncio
async def test_subscribe_target(client, auth_ctx):
    """Subscribe to a target returns status='subscribed'."""
    headers, _ = auth_ctx
    r = await client.post("/api/v1/approvals/subscribe-target",
                          json={"target": "EGFR"},
                          headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "subscribed"
    assert "EGFR" in data["targets"]


@pytest.mark.asyncio
async def test_subscribe_target_already(client, auth_ctx):
    """Subscribing the same target again returns status='already_subscribed'."""
    headers, _ = auth_ctx
    await client.post("/api/v1/approvals/subscribe-target",
                      json={"target": "EGFR"},
                      headers=headers)
    r = await client.post("/api/v1/approvals/subscribe-target",
                          json={"target": "EGFR"},
                          headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "already_subscribed"
    assert data["targets"] == ["EGFR"]


@pytest.mark.asyncio
async def test_list_subscribed_targets(client, auth_ctx):
    """List subscribed targets returns all targets."""
    headers, _ = auth_ctx
    # Subscribe to two targets
    await client.post("/api/v1/approvals/subscribe-target",
                      json={"target": "EGFR"},
                      headers=headers)
    await client.post("/api/v1/approvals/subscribe-target",
                      json={"target": "ALK"},
                      headers=headers)
    r = await client.get("/api/v1/approvals/subscribed-targets",
                         headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert set(data["targets"]) == {"EGFR", "ALK"}


@pytest.mark.asyncio
async def test_list_subscribed_targets_empty(client, auth_ctx):
    """List subscribed targets when none subscribed returns empty list."""
    headers, _ = auth_ctx
    r = await client.get("/api/v1/approvals/subscribed-targets",
                         headers=headers)
    assert r.status_code == 200
    assert r.json()["targets"] == []


@pytest.mark.asyncio
async def test_unsubscribe_target(client, auth_ctx):
    """Unsubscribe removes the target."""
    headers, _ = auth_ctx
    await client.post("/api/v1/approvals/subscribe-target",
                      json={"target": "EGFR"},
                      headers=headers)
    r = await client.post("/api/v1/approvals/unsubscribe-target",
                          json={"target": "EGFR"},
                          headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "unsubscribed"
    assert data["targets"] == []


@pytest.mark.asyncio
async def test_unsubscribe_nonexistent_target(client, auth_ctx):
    """Unsubscribe a target that was never subscribed is idempotent."""
    headers, _ = auth_ctx
    r = await client.post("/api/v1/approvals/unsubscribe-target",
                          json={"target": "NONEXISTENT"},
                          headers=headers)
    assert r.status_code == 200
    data = r.json()
    assert data["status"] == "unsubscribed"
    assert data["targets"] == []


@pytest.mark.asyncio
async def test_subscribe_target_unauthorized(client):
    """Subscribe without auth headers returns 401."""
    r = await client.post("/api/v1/approvals/subscribe-target",
                          json={"target": "EGFR"})
    assert r.status_code == 401
