"""公开 API：未登录用户可访问的文献浏览功能"""

import logging
from datetime import datetime, timedelta

from fastapi import APIRouter, Depends, Query, Request
from pydantic import BaseModel
from sqlalchemy import and_, func, select, text
from sqlalchemy.ext.asyncio import AsyncSession

from app.compat import UTC
from app.db import async_session, get_db
from app.models.interaction import AnonymousPageView
from app.models.literature import GlobalJournal, GlobalLiterature, GlobalLiteratureTag, GlobalTag
from app.services.hot_articles_cache import _build_cards, CACHE_KEY
from app.core.cache import cache
from app.services.homepage_feed_cache import CACHE_KEY as HOMEPAGE_CACHE_KEY
from app.schemas.literature import PublicFeedResponse, PublicLiteratureCard, PublicLiteratureDetail, cap_pub_date
from app.services.tag_loader import load_tags_for_literature

logger = logging.getLogger(__name__)

STATS_CACHE_KEY = "public:stats"

# pipeline 下一次执行时间（UTC），用于 stats 缓存 TTL
_NEXT_PIPELINE_UTC = {"hour": 3, "minute": 7}


async def refresh_stats_cache():
    """在 pipeline 完成后调用：重新计算 stats 并写入缓存"""
    async with async_session() as db:
        lit_total = (await db.execute(select(func.count(GlobalLiterature.id)))).scalar() or 0
        journal_total = (await db.execute(select(func.count(GlobalJournal.id)))).scalar() or 0
        week_ago = datetime.now(UTC) - timedelta(days=30)
        recent = (await db.execute(
            select(func.count(GlobalLiterature.id)).where(GlobalLiterature.created_at >= week_ago)
        )).scalar() or 0

        result = {
            "literature_total": lit_total,
            "journal_total": journal_total,
            "daily_avg_30d": round(recent / 30, 1) if lit_total > 0 else 0,
        }

    # 缓存到次日 pipeline 执行前（UTC 03:07）
    now = datetime.now(UTC)
    next_run = now.replace(hour=_NEXT_PIPELINE_UTC["hour"], minute=_NEXT_PIPELINE_UTC["minute"], second=0, microsecond=0)
    if now >= next_run:
        next_run += timedelta(days=1)
    ttl = int((next_run - now).total_seconds())

    await cache.set(STATS_CACHE_KEY, result, ttl=ttl)
    logger.info("Stats 缓存已更新：%d 篇/%d 刊，TTL=%ds", lit_total, journal_total, ttl)

router = APIRouter()


class AnonymousPageViewRequest(BaseModel):
    anonymous_id: str
    path: str
    page_title: str | None = None
    referer: str | None = None
    duration_seconds: int | None = None


@router.post("/page-view", status_code=204, summary="匿名页面访问记录")
async def track_anonymous_page_view(
    req: AnonymousPageViewRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
):
    """记录匿名用户页面访问（无需认证，受 RateLimitMiddleware 60 req/min/IP 限制）"""
    log = AnonymousPageView(
        anonymous_id=req.anonymous_id,
        path=req.path,
        page_title=req.page_title,
        ip_address=request.client.host if request.client else None,
        user_agent=(request.headers.get("user-agent") or "")[:500],
        referer=req.referer,
        duration_seconds=req.duration_seconds,
    )
    db.add(log)
    await db.commit()


@router.get("/literature/{pmid}", response_model=PublicLiteratureDetail, summary="公开详情")
async def public_literature_detail(pmid: int, db: AsyncSession = Depends(get_db)):
    """公开的文献详情，无需登录。仅返回基本信息，增值数据需登录查看。"""
    result = await db.execute(select(GlobalLiterature).where(GlobalLiterature.pmid == pmid))
    lit = result.scalar()
    if not lit:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="Literature not found")

    tags = []
    tag_result = await db.execute(
        select(GlobalTag, GlobalLiteratureTag.is_major)
        .join(GlobalTag, GlobalTag.id == GlobalLiteratureTag.tag_id)
        .where(
            GlobalLiteratureTag.literature_id == lit.id,
            GlobalTag.is_active == True,
        )
    )
    for tag, is_major in tag_result:
        tags.append({"id": str(tag.id), "name_zh": tag.name_zh, "name_en": tag.name_en,
                      "path": tag.path, "category": tag.tag_category, "is_major": is_major})

    authors = lit.authors or []
    return PublicLiteratureDetail(
        id=str(lit.id),
        pmid=lit.pmid,
        title=lit.title,
        abstract=lit.abstract,
        authors=authors,
        affiliation=authors[0].get("affiliation", "") if authors else "",
        doi=lit.doi,
        pmc_id=lit.pmc_id,
        is_oa=lit.is_oa,
        cited_by_count=lit.cited_by_count,
        journal=lit.journal,
        volume=lit.volume,
        issue=lit.issue,
        pages=lit.pages,
        journal_issn=lit.journal_issn,
        pub_date=cap_pub_date(lit.pub_date),
        pub_year=lit.pub_year,
        pub_types=lit.pub_types or [],
        keywords=lit.keywords or [],
        tags=tags,
        language=lit.language or "en",
        retracted=lit.retracted,
        retraction_details=lit.retraction_details,
        is_negative_result=lit.is_negative_result,
        grants=lit.grants or [],
        chemical_list=lit.chemical_list or [],
        gene_symbols=lit.gene_symbols or [],
        databank_list=lit.databank_list or [],
        suppl_mesh_list=lit.suppl_mesh_list or [],
        num_refs=lit.num_refs,
        publication_status=lit.publication_status,
        article_date=lit.article_date.isoformat() if lit.article_date else None,
        created_at=lit.created_at.isoformat() if lit.created_at else None,
        updated_at=lit.updated_at.isoformat() if lit.updated_at else None,
    )


TAGS_CACHE_KEY = "public:tags"
JOURNALS_CACHE_KEY = "public:journals"


@router.get("/tags", summary="标签树")
async def public_tags(db: AsyncSession = Depends(get_db)):
    """获取肿瘤科标签树（按层级组织），缓存 1 小时"""
    cached = await cache.get(TAGS_CACHE_KEY)
    if cached:
        return cached
    result = await db.execute(
        select(GlobalTag).where(GlobalTag.is_active == True)
        .order_by(GlobalTag.level, GlobalTag.sort_order)
    )
    tags = result.scalars().all()
    data = {
        "tags": [
            {
                "id": str(t.id), "name_zh": t.name_zh, "name_en": t.name_en,
                "path": t.path, "level": t.level, "parent_id": str(t.parent_id) if t.parent_id else None,
                "tag_category": t.tag_category, "is_selectable": t.is_selectable,
                "article_count": t.article_count,
            }
            for t in tags
        ],
        "categories": [
            {"key": "cancer", "label": "癌种部位"},
            {"key": "gene", "label": "驱动基因/靶点"},
            {"key": "treatment", "label": "治疗方式"},
            {"key": "study_type", "label": "研究类型"},
            {"key": "endpoint", "label": "临床终点/指标"},
            {"key": "scenario", "label": "临床场景"},
        ]
    }
    await cache.set(TAGS_CACHE_KEY, data, ttl=3600)
    return data


@router.get("/journals", summary="期刊列表")
async def public_journals(
    db: AsyncSession = Depends(get_db),
    page: int = Query(1, ge=1),
    page_size: int = Query(200, ge=10, le=500),
    tier: str = Query(""),
    q: str = Query(""),
):
    """期刊列表（支持分页、等级筛选、名称搜索），缓存 30 分钟"""
    # 缓存仅用于默认首页（无筛选、第一页）
    cache_key = None
    if not tier and not q and page == 1 and page_size == 200:
        cache_key = JOURNALS_CACHE_KEY
        cached = await cache.get(cache_key)
        if cached:
            return cached

    # 预聚合计数（避免 outerjoin + group_by 全表扫描）
    count_subq = (
        select(GlobalLiterature.journal_issn, func.count().label("cnt"))
        .group_by(GlobalLiterature.journal_issn)
        .subquery()
    )
    base = select(
        GlobalJournal.id, GlobalJournal.name, GlobalJournal.issn,
        GlobalJournal.tier, GlobalJournal.impact_factor,
        func.coalesce(count_subq.c.cnt, 0).label("article_count"),
    ).outerjoin(
        count_subq, GlobalJournal.issn == count_subq.c.journal_issn
    )

    if tier:
        base = base.where(GlobalJournal.tier == tier)
    if q:
        base = base.where(GlobalJournal.name.ilike(f"%{q}%"))

    total = (await db.execute(select(func.count()).select_from(base.subquery()))).scalar() or 0

    # 各等级期刊数（不随筛选变化，用于前端等级卡片计数）
    tc_rows = await db.execute(
        select(GlobalJournal.tier, func.count(GlobalJournal.id))
        .group_by(GlobalJournal.tier)
    )
    tier_counts = {str(t): c for t, c in tc_rows}

    rows = await db.execute(
        base.order_by(func.coalesce(count_subq.c.cnt, 0).desc(), GlobalJournal.name)
        .offset((page - 1) * page_size).limit(page_size)
    )
    journals = [
        {"id": str(r.id), "name": r.name, "issn": r.issn,
         "tier": r.tier, "impact_factor": r.impact_factor,
         "article_count": r.article_count}
        for r in rows
    ]
    data = {"journals": journals, "total": total, "page": page,
            "page_size": page_size, "tier_counts": tier_counts}
    if cache_key:
        await cache.set(cache_key, data, ttl=1800)
    return data


@router.get("/cancer-tags", summary="癌肿分类标签")
async def public_cancer_tags(db: AsyncSession = Depends(get_db)):
    """获取按癌种分类的标签层级树（tag_category='cancer'），含文献计数"""
    result = await db.execute(
        select(GlobalTag).where(GlobalTag.tag_category == "cancer", GlobalTag.is_active == True)
        .order_by(GlobalTag.level, GlobalTag.sort_order)
    )
    tags = result.scalars().all()
    data = [
        {
            "id": str(t.id), "name_zh": t.name_zh, "name_en": t.name_en,
            "path": t.path, "level": t.level,
            "parent_id": str(t.parent_id) if t.parent_id else None,
            "article_count": t.article_count,
            "is_selectable": t.is_selectable,
        }
        for t in tags
    ]
    return {"tags": data}


@router.get("/journals/{issn}", summary="期刊文献")
async def journal_articles(issn: str, db: AsyncSession = Depends(get_db), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100)):
    """某期刊下的最新文献"""
    offset = (page - 1) * page_size
    total_q = select(func.count(GlobalLiterature.id)).where(GlobalLiterature.journal_issn == issn)
    total = (await db.execute(total_q)).scalar() or 0
    result = await db.execute(
        select(GlobalLiterature).where(GlobalLiterature.journal_issn == issn)
        .order_by(GlobalLiterature.pub_date.desc().nullslast()).offset(offset).limit(page_size)
    )
    items = result.scalars().all()
    ids = [str(lit.id) for lit in items]
    tm = await load_tags_for_literature(db, ids)
    jname_map = {}
    jissns = list(set(lit.journal_issn for lit in items if lit.journal_issn))
    if jissns:
        jr = await db.execute(
            select(GlobalJournal.issn, GlobalJournal.name).where(GlobalJournal.issn.in_(jissns))
        )
        for issn_, name_ in jr.all():
            jname_map[issn_] = name_
    cards = _to_cards(items, tm, name_map=jname_map)
    return PublicFeedResponse(items=cards, total=total, date=f"journal_{issn}", must_read=0, recommended=0, related=len(cards))


@router.get("/stats", summary="公开统计")
async def public_stats():
    """返回平台公开统计数据（首页 Hero 使用），由 pipeline 完成后主动刷新缓存"""
    cached = await cache.get(STATS_CACHE_KEY)
    if cached:
        return cached
    # 缓存未命中（极少发生，仅服务重启后首次访问）→ 实时计算并缓存
    async with async_session() as db:
        lit_total = (await db.execute(select(func.count(GlobalLiterature.id)))).scalar() or 0
        journal_total = (await db.execute(select(func.count(GlobalJournal.id)))).scalar() or 0
        from datetime import datetime, timedelta
        from app.compat import UTC
        week_ago = datetime.now(UTC) - timedelta(days=30)
        recent = (await db.execute(
            select(func.count(GlobalLiterature.id)).where(GlobalLiterature.created_at >= week_ago)
        )).scalar() or 0
        result = {
            "literature_total": lit_total,
            "journal_total": journal_total,
            "daily_avg_30d": round(recent / 30, 1) if lit_total > 0 else 0,
        }
    # 缓存到次日 pipeline 执行前（UTC 03:07）
    now = datetime.now(UTC)
    next_run = now.replace(hour=_NEXT_PIPELINE_UTC["hour"], minute=_NEXT_PIPELINE_UTC["minute"], second=0, microsecond=0)
    if now >= next_run:
        next_run += timedelta(days=1)
    ttl = int((next_run - now).total_seconds())
    await cache.set(STATS_CACHE_KEY, result, ttl=ttl)
    return result


@router.get("/hot-articles", summary="热搜")
async def public_hot_articles(
    db: AsyncSession = Depends(get_db),
    page: int = Query(1, ge=1),
    page_size: int = Query(5, ge=1, le=30),
):
    """返回近期高被引/热门文献（优先缓存，定时任务每 30 分钟刷新）"""
    from datetime import datetime, timedelta
    from app.compat import UTC
    year_ago = datetime.now(UTC) - timedelta(days=365)

    # 尝试从缓存读取
    cached = await cache.get(CACHE_KEY)
    if cached and cached.get("items"):
        items = cached["items"]
        total = len(items)
        offset = (page - 1) * page_size
        paged = items[offset:offset + page_size]
        return {"items": paged, "total": total}

    # 缓存未命中 → 实时查询
    base_where = and_(GlobalLiterature.pub_date >= year_ago, GlobalLiterature.cited_by_count > 0)
    count_q = select(func.count(GlobalLiterature.id)).where(base_where)
    total = (await db.execute(count_q)).scalar() or 0

    offset = (page - 1) * page_size
    q = (
        select(GlobalLiterature)
        .where(base_where)
        .order_by(GlobalLiterature.cited_by_count.desc().nullslast(), GlobalLiterature.pub_date.desc().nullslast())
        .offset(offset).limit(page_size)
    )
    result = await db.execute(q)
    items = result.scalars().all()

    ids = [str(lit.id) for lit in items]
    tm = await load_tags_for_literature(db, ids)
    cards = _build_cards(items, tm)

    # 写回缓存（避免碎片化写入，仅第 1 页）
    if page == 1:
        await cache.set(CACHE_KEY, {"items": cards}, ttl=1800)

    return {"items": cards, "total": total}


@router.get("/homepage-feed", summary="首页 Feed")
async def public_homepage_feed(
    db: AsyncSession = Depends(get_db),
    page_size: int = Query(20, ge=1, le=50),
):
    """首页最新文献列表（优先缓存，定时任务每 30 分钟刷新）"""
    # 尝试从缓存读取
    cached = await cache.get(HOMEPAGE_CACHE_KEY)
    if cached and cached.get("items"):
        items = cached["items"]
        return {"items": items[:page_size], "has_more": len(items) > page_size}

    # 缓存未命中 → 实时简化查询（多取 1 条检测 has_more）
    q = (
        select(
            GlobalLiterature.id,
            GlobalLiterature.pmid,
            GlobalLiterature.title,
            GlobalLiterature.authors,
            GlobalLiterature.journal,
            GlobalLiterature.journal_issn,
            GlobalLiterature.pub_date,
            GlobalLiterature.pub_year,
            GlobalLiterature.doi,
            GlobalLiterature.pmc_id,
            GlobalLiterature.is_oa,
            GlobalLiterature.cited_by_count,
            GlobalLiterature.pub_types,
            GlobalLiterature.article_date,
            GlobalLiterature.ai_summary,
            GlobalLiterature.retracted,
            GlobalLiterature.is_negative_result,
            GlobalLiterature.created_at,
            GlobalLiterature.updated_at,
        )
        .order_by(GlobalLiterature.pub_date.desc().nullslast())
        .limit(page_size + 1)
    )
    result = await db.execute(q)
    rows = result.all()

    has_more = len(rows) > page_size
    items = rows[:page_size]

    # Batch load tags
    lit_ids = [str(r.id) for r in items]
    tags_map = await load_tags_for_literature(db, lit_ids)

    # Batch load journal tiers + canonical names
    tier_map = {}
    name_map = {}
    issns = list(set(r.journal_issn for r in items if r.journal_issn))
    if issns:
        jr = await db.execute(
            select(GlobalJournal.issn, GlobalJournal.tier, GlobalJournal.name).where(GlobalJournal.issn.in_(issns))
        )
        for issn_, tier_, name_ in jr.all():
            tier_map[issn_] = tier_
            name_map[issn_] = name_

    cards = []
    for r in items:
        authors = r.authors or []
        first_author = authors[0].get("family", "") if authors else ""
        affiliation = _get_affiliation(authors)
        tlist = tags_map.get(str(r.id), [])
        ai = r.ai_summary.get("one_liner") if r.ai_summary and isinstance(r.ai_summary, dict) else None
        journal = name_map.get(r.journal_issn) or r.journal
        cards.append({
            "id": str(r.id), "pmid": r.pmid, "title": r.title,
            "first_author": first_author, "affiliation": affiliation,
            "journal": journal, "journal_issn": r.journal_issn,
            "journal_tier": tier_map.get(r.journal_issn),
            "pub_date": cap_pub_date(r.pub_date),
            "pub_year": r.pub_year, "doi": r.doi,
            "pmc_id": r.pmc_id, "is_oa": r.is_oa,
            "cited_by_count": r.cited_by_count or 0,
            "pub_types": r.pub_types,
            "article_date": r.article_date.isoformat() if r.article_date else None,
            "retracted": r.retracted,
            "is_negative_result": r.is_negative_result,
            "tags": tlist,
            "ai_summary": ai,
            "created_at": r.created_at.isoformat() if r.created_at else None,
            "updated_at": r.updated_at.isoformat() if r.updated_at else None,
        })
    return {"items": cards, "has_more": has_more}


def _get_affiliation(authors: list) -> str | None:
    """取第一个有机构的作者信息，缩写机构名到20字以内"""
    if not authors: return None
    for a in authors:
        aff = a.get("affiliation", "") or ""
        if aff.strip():
            # 取第一个逗号前或前20字
            short = aff.split(",")[0].strip()
            return short[:30] + ("..." if len(short) > 30 else "")
    return None


def _to_cards(items, tags_map: dict[str, list] = None, ai_map: dict[str, str] = None,
              name_map: dict[str, str] = None) -> list[PublicLiteratureCard]:
    cards = []
    for lit in items:
        authors = lit.authors or []
        first_author = authors[0].get("family", "") if authors else ""
        affiliation = _get_affiliation(authors)
        tlist = tags_map.get(str(lit.id), []) if tags_map else []
        enriched_tags = []
        for t in tlist:
            tag = dict(t)
            if '::' in tag.get('path', ''):
                tag['name_zh'] = tag['path'].split('::')[-1]
            enriched_tags.append(tag)
        ai = (ai_map or {}).get(str(lit.id))
        journal = name_map.get(lit.journal_issn) if name_map and lit.journal_issn else lit.journal
        cards.append(PublicLiteratureCard(
            id=str(lit.id), pmid=lit.pmid, title=lit.title,
            first_author=first_author, affiliation=affiliation, journal=journal or lit.journal,
            pub_date=cap_pub_date(lit.pub_date),
            article_date=lit.article_date.isoformat() if lit.article_date else None,
            cited_by_count=lit.cited_by_count or 0,
            tags=enriched_tags, ai_summary=ai,
            retracted=lit.retracted,
            is_negative_result=lit.is_negative_result,
            created_at=lit.created_at.isoformat() if lit.created_at else None,
            updated_at=lit.updated_at.isoformat() if lit.updated_at else None,
        ))
    return cards
