"""
Full citation refresh - standalone script using direct SQL.
No app imports needed.
"""
import asyncio, sys
from datetime import datetime, timezone
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine

DB = 'postgresql+asyncpg://scilit:scilit_prod_2026@postgres:5432/scilit'
LOG = '/tmp/citation_refresh2.log'

ELINK_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi"
BATCH = 200  # NCBI max per request
RATE = 3.0   # req/s
INTERVAL = 1.0 / RATE

def log(msg):
    ts = datetime.now(timezone.utc).isoformat()
    line = f'[{ts}] {msg}\n'
    print(line, end='', flush=True)
    with open(LOG, 'a') as f:
        f.write(line)

async def fetch_citedby(pmids):
    """Fetch citation counts for a batch of PMIDs via NCBI elink."""
    import httpx
    import xml.etree.ElementTree as ET

    pmid_str = ','.join(str(p) for p in pmids)
    url = f"{ELINK_URL}?dbfrom=pubmed&linkname=pubmed_pubmed_citedin&id={pmid_str}&retmode=xml"

    async with httpx.AsyncClient(timeout=30.0) as client:
        resp = await client.get(url)
        resp.raise_for_status()

    root = ET.fromstring(resp.content)
    result = {}
    for linkset in root.findall(".//LinkSet"):
        src_id_elem = linkset.find("IdList/Id")
        if src_id_elem is None or not src_id_elem.text:
            continue
        src_pmid = int(src_id_elem.text.strip())
        count = 0
        for lsdb in linkset.findall(".//LinkSetDb"):
            ln = lsdb.findtext("LinkName", "")
            if ln == "pubmed_pubmed_citedin":
                count = len([e for e in lsdb.findall(".//Link/Id") if e.text])
                break
        result[src_pmid] = max(result.get(src_pmid, 0), count)

    return result

async def main():
    log('Starting citation refresh (standalone)...')
    engine = create_async_engine(DB, pool_size=4)

    # Get all PMIDs
    async with engine.connect() as c:
        rows = (await c.execute(text("SELECT pmid FROM global_literature WHERE pmid IS NOT NULL ORDER BY created_at DESC"))).all()
    targets = [r[0] for r in rows]
    total = len(targets)
    log(f'Found {total} PMIDs')

    total_updated = 0
    total_errors = 0

    # Process ALL PMIDs via elink (the function handles its own batching)
    # We do it in chunks of 5000 for progress reporting and recovery
    CHUNK = 5000
    for i in range(0, total, CHUNK):
        chunk = targets[i:i+CHUNK]
        cn = i // CHUNK + 1
        tc = (total + CHUNK - 1) // CHUNK
        log(f'Chunk {cn}/{tc}: {len(chunk)} PMIDs ({i}/{total}, {i*100//total}%)')

        # Process each chunk's PMIDs via elink (internal 200/batch)
        all_counts = {}
        for j in range(0, len(chunk), BATCH):
            batch = chunk[j:j+BATCH]
            try:
                counts = await fetch_citedby(batch)
                all_counts.update(counts)
            except Exception as e:
                log(f'  elink sub-batch error: {e}')
            await asyncio.sleep(INTERVAL)

        # Write to DB
        updated = 0
        async with engine.connect() as c:
            for pmid, count in all_counts.items():
                await c.execute(
                    text("UPDATE global_literature SET cited_by_count = :c, updated_at = NOW() WHERE pmid = :p"),
                    {"c": count, "p": pmid},
                )
                updated += 1
            await c.commit()

        total_updated += updated
        total_errors += len(chunk) - len(all_counts)
        log(f'  => +{updated} updated ({total_updated}/{total})')

    log(f'DONE. Updated: {total_updated}, Errors: {total_errors}')
    await engine.dispose()

asyncio.run(main())
