"""运营分析查询服务：会话/漏斗/留存"""

import uuid
from datetime import datetime, timedelta

from sqlalchemy import Date, cast, func, select, text
from sqlalchemy.ext.asyncio import AsyncSession

from app.compat import UTC
from app.models.interaction import UserActivityLog


async def get_sessions(
    db: AsyncSession,
    tenant_id: uuid.UUID | None = None,
    days: int = 7,
    page: int = 1,
    page_size: int = 30,
):
    """按 session_id 聚合查询会话列表"""
    since = datetime.now(UTC) - timedelta(days=days)

    # 先查有数据的 session_ids
    subq = (
        select(
            UserActivityLog.session_id,
            func.min(UserActivityLog.created_at).label("started_at"),
            func.max(UserActivityLog.created_at).label("ended_at"),
            func.count(UserActivityLog.id).label("event_count"),
            func.sum(
                func.coalesce(UserActivityLog.duration_seconds, 0)
            ).label("total_duration"),
        )
        .where(
            UserActivityLog.session_id.isnot(None),
            UserActivityLog.session_id != "",
            UserActivityLog.created_at >= since,
        )
    )
    if tenant_id:
        subq = subq.where(UserActivityLog.tenant_id == tenant_id)

    # 子查询 GROUP BY session_id
    session_groups = subq.group_by(UserActivityLog.session_id).subquery()

    # 查询入口页和出口页（第一条和最后一条 activity）
    entry_subq = (
        select(
            UserActivityLog.session_id,
            UserActivityLog.target_id.label("entry_page"),
            func.row_number().over(
                partition_by=UserActivityLog.session_id,
                order_by=UserActivityLog.created_at,
            ).label("rn"),
        )
        .where(
            UserActivityLog.session_id.isnot(None),
            UserActivityLog.session_id != "",
            UserActivityLog.created_at >= since,
        )
    )
    if tenant_id:
        entry_subq = entry_subq.where(UserActivityLog.tenant_id == tenant_id)

    entry_subq = entry_subq.subquery()

    exit_subq = (
        select(
            UserActivityLog.session_id,
            UserActivityLog.target_id.label("exit_page"),
            func.row_number().over(
                partition_by=UserActivityLog.session_id,
                order_by=UserActivityLog.created_at.desc(),
            ).label("rn"),
        )
        .where(
            UserActivityLog.session_id.isnot(None),
            UserActivityLog.session_id != "",
            UserActivityLog.created_at >= since,
        )
    )
    if tenant_id:
        exit_subq = exit_subq.where(UserActivityLog.tenant_id == tenant_id)

    exit_subq = exit_subq.subquery()

    # 最后统计用户数
    user_subq = (
        select(
            UserActivityLog.session_id,
            func.count(func.distinct(UserActivityLog.user_id)).label("user_count"),
        )
        .where(
            UserActivityLog.session_id.isnot(None),
            UserActivityLog.session_id != "",
            UserActivityLog.created_at >= since,
        )
    )
    if tenant_id:
        user_subq = user_subq.where(UserActivityLog.tenant_id == tenant_id)
    user_subq = user_subq.group_by(UserActivityLog.session_id).subquery()

    # 合并查询
    total_q = select(func.count()).select_from(session_groups)
    total = (await db.execute(total_q)).scalar() or 0

    q = (
        select(
            session_groups.c.session_id,
            session_groups.c.started_at,
            session_groups.c.ended_at,
            session_groups.c.event_count,
            session_groups.c.total_duration,
            entry_subq.c.entry_page,
            exit_subq.c.exit_page,
            user_subq.c.user_count,
        )
        .outerjoin(
            entry_subq,
            (session_groups.c.session_id == entry_subq.c.session_id)
            & (entry_subq.c.rn == 1),
        )
        .outerjoin(
            exit_subq,
            (session_groups.c.session_id == exit_subq.c.session_id)
            & (exit_subq.c.rn == 1),
        )
        .outerjoin(
            user_subq,
            session_groups.c.session_id == user_subq.c.session_id,
        )
        .order_by(session_groups.c.started_at.desc())
        .offset((page - 1) * page_size)
        .limit(page_size)
    )

    rows = await db.execute(q)
    items = []
    for row in rows:
        items.append({
            "session_id": row.session_id,
            "started_at": row.started_at.isoformat() if row.started_at else None,
            "ended_at": row.ended_at.isoformat() if row.ended_at else None,
            "event_count": row.event_count,
            "duration_seconds": row.total_duration or 0,
            "entry_page": row.entry_page,
            "exit_page": row.exit_page,
            "user_count": row.user_count or 0,
        })

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


async def get_session_trend(db: AsyncSession, tenant_id: uuid.UUID | None = None, days: int = 30):
    """每日会话趋势"""
    since = datetime.now(UTC) - timedelta(days=days - 1)

    q = (
        select(
            cast(UserActivityLog.created_at, Date).label("day"),
            func.count(func.distinct(UserActivityLog.session_id)).label("session_count"),
            func.count(func.distinct(UserActivityLog.user_id)).label("unique_users"),
            func.avg(func.coalesce(UserActivityLog.duration_seconds, 0)).label("avg_duration"),
        )
        .where(
            UserActivityLog.session_id.isnot(None),
            UserActivityLog.session_id != "",
            UserActivityLog.created_at >= since,
        )
        .group_by(text("day"))
        .order_by(text("day"))
    )
    if tenant_id:
        q = q.where(UserActivityLog.tenant_id == tenant_id)

    rows = await db.execute(q)
    items = []
    for row in rows:
        items.append({
            "date": str(row.day),
            "sessions": row.session_count,
            "unique_users": row.unique_users,
            "avg_duration_seconds": round(float(row.avg_duration or 0), 1),
        })

    return {"items": items}


async def get_funnel(db: AsyncSession, tenant_id: uuid.UUID | None = None, days: int = 30):
    """行为漏斗：page_view → search → view_literature → save_literature"""
    since = datetime.now(UTC) - timedelta(days=days)

    steps = ["page_view", "search", "view_literature", "save_literature", "export"]
    step_labels = {
        "page_view": "访问页面",
        "search": "执行搜索",
        "view_literature": "查看文献",
        "save_literature": "收藏文献",
        "export": "导出文献",
    }

    funnel = []
    prev_count = None
    for step in steps:
        q = select(func.count(func.distinct(UserActivityLog.user_id))).where(
            UserActivityLog.activity_type == step,
            UserActivityLog.created_at >= since,
        )
        if tenant_id:
            q = q.where(UserActivityLog.tenant_id == tenant_id)
        cnt = (await db.execute(q)).scalar() or 0
        funnel.append({
            "step": step,
            "label": step_labels.get(step, step),
            "users": cnt,
            "conversion_rate": round(cnt / prev_count * 100, 1) if prev_count and prev_count > 0 else 100.0,
        })
        prev_count = cnt

    return {"funnel": funnel, "days": days}


async def get_retention(db: AsyncSession, tenant_id: uuid.UUID | None = None, weeks: int = 12):
    """周级留存 cohort 分析（兼容 SQLite + PostgreSQL）"""
    from collections import defaultdict

    since = datetime.now(UTC) - timedelta(weeks=weeks * 2)

    # 获取所有用户及其最早活动日期
    user_first_raw = await db.execute(
        select(
            UserActivityLog.user_id,
            func.min(cast(UserActivityLog.created_at, Date)).label("first_date"),
        )
        .where(UserActivityLog.user_id.isnot(None), UserActivityLog.created_at >= since)
        .group_by(UserActivityLog.user_id)
    )

    # Python: compute cohort week (ISO week start = Monday)
    def _week_start(d):
        """Return the Monday of the ISO week for d."""
        return d - timedelta(days=d.weekday())

    user_cohort: dict[uuid.UUID, str] = {}
    for uid, first_date in user_first_raw:
        if first_date:
            user_cohort[uid] = _week_start(first_date).isoformat()

    # Group users by cohort
    cohort_users: dict[str, set[uuid.UUID]] = defaultdict(set)
    for uid, cw in user_cohort.items():
        cohort_users[cw].add(uid)

    # Get all user activity dates
    user_dates_raw = await db.execute(
        select(
            UserActivityLog.user_id,
            cast(UserActivityLog.created_at, Date).label("activity_date"),
        )
        .where(
            UserActivityLog.user_id.isnot(None),
            UserActivityLog.user_id.in_(list(user_cohort.keys())),
            UserActivityLog.created_at >= since,
        )
        .distinct()
    )

    # Map user → set of active weeks
    user_active_weeks: dict[uuid.UUID, set[str]] = defaultdict(set)
    for uid, ad in user_dates_raw:
        if ad:
            user_active_weeks[uid].add(_week_start(ad).isoformat())

    # Build cohort matrix
    cohorts = []
    for cohort_key in sorted(cohort_users.keys(), reverse=True)[:weeks]:
        users = cohort_users[cohort_key]
        total = len(users)
        if total == 0:
            continue
        row = {"cohort": cohort_key, "total": total, "weeks": []}
        for offset in range(weeks):
            wk = _week_start(
                datetime.fromisoformat(cohort_key) + timedelta(weeks=offset)
            ).isoformat()
            active = sum(1 for u in users if wk in user_active_weeks[u])
            retention_rate = round(active / total * 100, 1) if total > 0 else 0
            row["weeks"].append({
                "week": offset,
                "active_users": active,
                "retention_rate": retention_rate,
            })
        cohorts.append(row)

    return {"cohorts": cohorts}
