"""引用次数更新服务：通过 Europe PMC API 获取引用计数"""

import asyncio
import logging

import httpx
from sqlalchemy import func, select, update

from app.compat import UTC

logger = logging.getLogger(__name__)
from app.db import async_session
from app.models.literature import GlobalLiterature

EUROPE_PMC_URL = "https://www.ebi.ac.uk/europepmc/webservices/rest/search"

# 重试配置
MAX_RETRIES = 3
RETRYABLE_CODES = {429, 500, 502, 503, 504}


async def _fetch_with_retry(client: httpx.AsyncClient, url: str, params: dict) -> dict:
    """带指数退避重试的 HTTP GET"""
    last_exc = None
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            r = await client.get(url, params=params)
            if r.status_code in RETRYABLE_CODES:
                raise httpx.HTTPStatusError(f"Retryable status {r.status_code}", request=r.request, response=r)
            r.raise_for_status()
            return r.json()
        except (httpx.TimeoutException, httpx.NetworkError) as e:
            last_exc = e
            if attempt == MAX_RETRIES:
                raise
            await asyncio.sleep(2 ** attempt)
        except httpx.HTTPStatusError as e:
            last_exc = e
            if e.response.status_code in RETRYABLE_CODES and attempt < MAX_RETRIES:
                await asyncio.sleep(2 ** attempt)
            else:
                raise
    raise last_exc or RuntimeError("Unexpected retry exit")


async def update_citation_counts(db, lit_ids: list[int]) -> int:
    """批量查询引用次数并更新数据库，返回更新的文献数"""
    if not lit_ids:
        return 0

    updated = 0
    # Europe PMC 一次最多查 100 篇
    for batch_start in range(0, len(lit_ids), 50):
        batch = lit_ids[batch_start:batch_start + 50]
        # 构造查询：EXT_ID:pmid1 OR EXT_ID:pmid2 OR ...
        query = " OR ".join(f"EXT_ID:{p}" for p in batch)
        params = {"query": query, "pageSize": 100, "format": "json"}

        async with httpx.AsyncClient(timeout=30) as client:
            try:
                data = await _fetch_with_retry(client, EUROPE_PMC_URL, params)
                for result in data.get("resultList", {}).get("result", []):
                    pmid = result.get("extId")
                    cited = result.get("citedByCount", 0)
                    if pmid:
                        await db.execute(
                            update(GlobalLiterature)
                            .where(GlobalLiterature.pmid == int(pmid))
                            .values(cited_by_count=cited, updated_at=func.now())
                        )
                        updated += 1
            except (httpx.TimeoutException, httpx.HTTPStatusError, httpx.NetworkError) as e:
                logger.warning("Europe PMC batch error after retries: %s", e)
                continue
        await db.commit()
        await asyncio.sleep(0.5)  # 限速

    return updated


async def run_citation_update(cached_hours: int = 24):
    """主入口：查询需要更新的文献并更新引用次数"""
    async with async_session() as db:
        from datetime import datetime, timedelta
        cutoff = datetime.now(UTC) - timedelta(hours=cached_hours)

        # 先处理引用计数为 0 的文献，再处理已过期的文献
        result = await db.execute(
            select(GlobalLiterature.pmid)
            .where(
                GlobalLiterature.cited_by_count == 0
            )
            .order_by(GlobalLiterature.updated_at.asc())
            .limit(5000)
        )
        pmids = [r for (r,) in result.all()]

        # 如果没有引用为 0 的文献，退回到按过期时间刷新
        if not pmids:
            result = await db.execute(
                select(GlobalLiterature.pmid)
                .where(GlobalLiterature.updated_at < cutoff)
                .order_by(GlobalLiterature.updated_at.asc())
                .limit(5000)
            )
            pmids = [r for (r,) in result.all()]

        if not pmids:
            return {"checked": 0, "updated": 0}

        updated = await update_citation_counts(db, pmids)
        return {"checked": len(pmids), "updated": updated}
