"""FDA 药品标签同步服务（openFDA labeling endpoint）

从 FDA label API 获取适应症原文、临床试验描述、作用机制等结构化数据。
标签按 set_id 去重存储，一个 NDA/BLA 可对应多个 label 版本。

API: https://open.fda.gov/apis/drug/label/
"""

import logging
import re
from datetime import datetime

import httpx

from app.db import async_session
from app.models.business import DrugApproval, DrugLabel
from app.services.fda_api import _rate_limited_get, FDA_API_BASE

logger = logging.getLogger(__name__)


async def sync_drug_labels() -> dict:
    """同步现有 DrugApproval 所有唯一 (drug_name, generic_name) 的 FDA 标签"""
    stats = {"fetched": 0, "upserted": 0, "skipped": 0, "errors": 0}

    # 获取需要查询的去重药品列表
    async with async_session() as db:
        from sqlalchemy import select, distinct
        r = await db.execute(
            select(distinct(DrugApproval.drug_name), DrugApproval.generic_name)
            .where(DrugApproval.approval_agency == "FDA")
            .order_by(DrugApproval.drug_name)
        )
        drugs = r.all()

    logger.info("Syncing labels for %d unique FDA drugs...", len(drugs))

    async with httpx.AsyncClient() as client:
        for drug_name, generic_name in drugs:
            try:
                n = await _fetch_and_store_label(client, drug_name, generic_name)
                stats["upserted"] += n
                stats["fetched"] += 1
            except Exception:
                logger.exception("Error syncing label for %s", drug_name)
                stats["errors"] += 1

    logger.info(
        "Label sync done: %d drugs, %d labels upserted, %d errors",
        stats["fetched"], stats["upserted"], stats["errors"],
    )
    return stats


async def _fetch_and_store_label(
    client: httpx.AsyncClient,
    drug_name: str,
    generic_name: str,
) -> int:
    """为单个药品获取并存储标签。返回 upsert 的 label 数"""
    # 按品牌名搜索 label
    query = f'openfda.brand_name:"{drug_name}"'
    data = await _rate_limited_get(
        client,
        f"{FDA_API_BASE}/drug/label.json",
        {"search": query, "limit": 25},
    )
    if not data or not data.get("results"):
        # 如果品牌名无结果，用通用名
        query = f'openfda.generic_name:"{generic_name}"'
        data = await _rate_limited_get(
            client,
            f"{FDA_API_BASE}/drug/label.json",
            {"search": query, "limit": 25},
        )
    if not data or not data.get("results"):
        logger.debug("No label found for %s", drug_name)
        return 0

    count = 0
    for label in data["results"]:
        ofda = label.get("openfda", {})
        set_id = label.get("set_id")
        if not set_id:
            continue
        app_number = _first(ofda, "application_number")
        label_brand = _first(ofda, "brand_name") or drug_name
        label_generic = _first(ofda, "generic_name") or generic_name

        cs_text = _section(label, "clinical_studies")
        ncts = _extract_ncts(cs_text)

        await _upsert_label(
            set_id=set_id,
            application_number=app_number,
            drug_name=label_brand,
            generic_name=label_generic,
            effective_time=label.get("effective_time"),
            indications_and_usage=_section(label, "indications_and_usage"),
            mechanism_of_action=_section(label, "mechanism_of_action"),
            clinical_studies=cs_text,
            warnings=_section(label, "warnings_and_cautions"),
            adverse_reactions=_section(label, "adverse_reactions"),
            nct_numbers=ncts,
        )
        count += 1

    return count


async def _upsert_label(**kwargs):
    """按 set_id 去重写入 drug_labels"""
    from sqlalchemy import select, update
    async with async_session() as db:
        try:
            existing = await db.execute(
                select(DrugLabel).where(DrugLabel.set_id == kwargs["set_id"])
            )
            if existing.scalar():
                await db.execute(
                    update(DrugLabel)
                    .where(DrugLabel.set_id == kwargs["set_id"])
                    .values(
                        effective_time=kwargs["effective_time"],
                        indications_and_usage=kwargs["indications_and_usage"],
                        mechanism_of_action=kwargs["mechanism_of_action"],
                        clinical_studies=kwargs["clinical_studies"],
                        warnings=kwargs["warnings"],
                        adverse_reactions=kwargs["adverse_reactions"],
                        nct_numbers=kwargs["nct_numbers"],
                        updated_at=datetime.now(),
                    )
                )
            else:
                from app.db import new_uuid
                dl = DrugLabel(
                    id=new_uuid(),
                    set_id=kwargs["set_id"],
                    application_number=kwargs["application_number"],
                    drug_name=kwargs["drug_name"],
                    generic_name=kwargs["generic_name"],
                    effective_time=kwargs["effective_time"],
                    indications_and_usage=kwargs["indications_and_usage"],
                    mechanism_of_action=kwargs["mechanism_of_action"],
                    clinical_studies=kwargs["clinical_studies"],
                    warnings=kwargs["warnings"],
                    adverse_reactions=kwargs["adverse_reactions"],
                    nct_numbers=kwargs["nct_numbers"],
                )
                db.add(dl)
            await db.commit()
        except Exception:
            await db.rollback()
            raise


def _first(ofda: dict, field: str, default: str = "") -> str:
    vals = ofda.get(field, [])
    return str(vals[0]) if isinstance(vals, list) and vals else default


def _section(label: dict, field: str) -> str | None:
    """提取 label 中的某个 section 文本"""
    vals = label.get(field, [])
    if isinstance(vals, list) and vals:
        text = "\n".join(str(v) for v in vals if v).strip()
        return text if text else None
    return None


def _extract_ncts(text: str | None) -> list[str] | None:
    """从临床研究文本中提取所有 NCT 编号"""
    if not text:
        return None
    ncts = re.findall(r"NCT\d{8}", text)
    return sorted(set(ncts)) if ncts else None
