"""PMC OA 全文 TDM 客户端：通过 NCBI E-utilities efetch 获取 JATS XML"""

import asyncio
import logging

import httpx

from app.config import settings

logger = logging.getLogger(__name__)

EFETCH_PMC_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"
API_KEY = settings.PUBMED_API_KEY or None
MAX_RETRIES = 3

# 与 PubMed E-utilities 共用限速池
PMC_RATE_LIMIT = asyncio.Lock()
RATE_LIMIT_INTERVAL = 0.35  # ~3 req/s（无 API Key）


async def fetch_pmc_xml(pmc_id: str, client: httpx.AsyncClient | None = None) -> str | None:
    """通过 efetch 获取 PMC 全文 JATS XML。pmc_id 为纯数字（如 '1234567'）。

    返回原始 XML 字符串（~50-200KB），失败返回 None。
    """
    if not pmc_id:
        return None

    own_client = client is None
    if own_client:
        client = httpx.AsyncClient(timeout=60.0)

    try:
        params = {
            "db": "pmc",
            "id": pmc_id,
            "retmode": "xml",
        }
        if API_KEY:
            params["api_key"] = API_KEY

        async with PMC_RATE_LIMIT:
            for attempt in range(1, MAX_RETRIES + 1):
                try:
                    r = await client.get(EFETCH_PMC_URL, params=params)
                    if r.status_code in (429, 500, 502, 503, 504) and attempt < MAX_RETRIES:
                        await asyncio.sleep(2 ** attempt)
                        continue
                    r.raise_for_status()
                    text = r.text
                    if len(text) < 500 or "<error" in text.lower():
                        logger.warning("pmc_oa_empty pmid=%s len=%d", pmc_id, len(text))
                        return None
                    return text
                except (httpx.TimeoutException, httpx.NetworkError) as e:
                    logger.warning("pmc_oa_retry pmid=%s attempt=%d %s", pmc_id, attempt, e)
                    if attempt == MAX_RETRIES:
                        return None
                    await asyncio.sleep(2 ** attempt)

            await asyncio.sleep(RATE_LIMIT_INTERVAL)
            return None
    except Exception:
        logger.warning("pmc_oa_failed pmid=%s", pmc_id, exc_info=True)
        return None
    finally:
        if own_client:
            await client.aclose()
