"""标签订阅 + 个人偏好 + 计费 + 升级 + 微信支付 API"""

import uuid
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel
from sqlalchemy import func, select, case, or_
from sqlalchemy.ext.asyncio import AsyncSession

from app.compat import UTC
from app.core.permissions import get_current_user
from app.core.plans import PLANS, get_tenant_plan
from app.core.tenant_context import tenant_ctx
from app.db import get_db
from app.models.interaction import UserLiterature, UserSubscription
from app.models.literature import GlobalLiteratureTag, GlobalTag, GlobalTagTreeNumber
from app.models.operations import PaymentOrder, SubscriptionEvent
from app.models.user import Tenant
from app.services.feed_engine import batch_generate_feeds
from app.services.wechat_pay import (
    PLAN_PRICES,
    create_unified_order,
    fail_xml,
    generate_out_trade_no,
    is_dev_mode,
    success_xml,
    verify_notify,
)

router = APIRouter()


def uid(u: dict) -> uuid.UUID:
    return uuid.UUID(u["sub"])


class SubscriptionToggle(BaseModel):
    tag_id: str; match_mode: str = "standard"; subscribe: bool = True


class BatchSubscribe(BaseModel):
    tag_ids: list[str]; match_mode: str = "standard"; include_children: bool = True


class UserPreferences(BaseModel):
    journal_tiers: list[str] = []       # tier1, tier2, tier3, tier4
    therapy_types: list[str] = []       # targeted_therapy, immunotherapy, chemotherapy, radiotherapy, surgery, hormone
    lit_types: list[str] = []           # rct, meta_analysis, guideline, review
    match_mode: str = "standard"        # standard / loose / strict


class UpgradeRequest(BaseModel):
    target_plan: str  # pro | team


# ─── 内部辅助 ───

async def _do_upgrade(db: AsyncSession, tenant_id: uuid.UUID, old_plan: str, target_plan: str) -> dict:
    """执行租户方案升级（不检查支付，供内部和回调使用）"""
    t = await db.get(Tenant, tenant_id)
    if not t:
        raise HTTPException(404, "Tenant not found")
    t.plan_type = target_plan
    if target_plan != "free":
        t.is_personal = False
    if target_plan == "team":
        t.max_members = 100
    db.add(SubscriptionEvent(
        id=uuid.uuid4(), tenant_id=t.id, event_type="upgrade",
        from_plan=old_plan, to_plan=target_plan, event_metadata={},
    ))
    await db.commit()
    return {"status": "upgraded", "plan": target_plan, "old_plan": old_plan}


# ─── 方案与标签订阅 ───

@router.get("/plans", summary="方案列表")
async def list_plans():
    return {"plans": [{"key": k, "name": v["name"], "features": v} for k, v in PLANS.items()]}


@router.get("/my-plan", summary="我的方案")
async def my_plan(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    plan = await get_tenant_plan(db)
    saved = (await db.execute(select(func.count(UserLiterature.id)).where(UserLiterature.user_id == uid(user)))).scalar() or 0
    raw_tid = tenant_ctx.get()
    plan_key = "free"
    if raw_tid:
        r = await db.execute(select(Tenant.plan_type).where(Tenant.id == uuid.UUID(raw_tid)))
        plan_key = r.scalar() or "free"
    return {"plan": plan["name"], "plan_key": plan_key, "features": plan,
            "usage": {"saved_items": saved, "saved_limit": plan["max_saved_items"], "api_quota": plan["api_quota_per_day"]}}


@router.get("/my-tags", response_model=list[dict], summary="我的订阅")
async def list_subscriptions(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    result = await db.execute(
        select(UserSubscription, GlobalTag).join(GlobalTag, UserSubscription.tag_id == GlobalTag.id)
        .where(UserSubscription.user_id == uid(user), UserSubscription.is_active == True))
    return [{"id": str(sub.id), "tag_id": str(tag.id), "tag_name": tag.name_zh or tag.name_en or "", "tag_path": tag.path,
              "tag_category": tag.tag_category, "match_mode": sub.match_mode, "is_active": sub.is_active}
            for sub, tag in result]


@router.get("/preferences", summary="获取个人偏好")
async def get_preferences(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    """获取用户的推送偏好设置（期刊等级、治疗类型、文献类型等）"""
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return UserPreferences().model_dump()
    t = await db.get(Tenant, uuid.UUID(raw_tid))
    if not t:
        return UserPreferences().model_dump()
    prefs = (t.settings or {}).get(f"user_prefs:{uid(user)}", {})
    defaults = UserPreferences().model_dump()
    defaults.update(prefs)
    return defaults


@router.post("/preferences", summary="保存个人偏好")
async def save_preferences(req: UserPreferences, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    """保存推送偏好设置"""
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return {"status": "saved", "preferences": req.model_dump()}
    t = await db.get(Tenant, uuid.UUID(raw_tid))
    if not t:
        return {"status": "saved", "preferences": req.model_dump()}
    settings = dict(t.settings or {})
    settings[f"user_prefs:{uid(user)}"] = req.model_dump()
    t.settings = settings
    await db.commit()
    return {"status": "saved", "preferences": req.model_dump()}


@router.get("/estimate", summary="预估推送量")
async def estimate_subscription(
    tag_ids: str = Query("", description="逗号分隔的标签 UUID"),
    match_mode: str = Query("standard"),
    db: AsyncSession = Depends(get_db),
    user: dict = Depends(get_current_user),
):
    """根据选择的标签和匹配模式，预估可覆盖文献量和每日推送量"""
    if not tag_ids:
        return {"total_articles": 0, "daily_estimate": 0, "by_priority": {"must_read": 0, "recommended": 0, "related": 0}}

    ids = []
    for raw in tag_ids.split(","):
        try:
            ids.append(uuid.UUID(raw.strip()))
        except (ValueError, AttributeError):
            continue
    if not ids:
        return {"total_articles": 0, "daily_estimate": 0, "by_priority": {"must_read": 0, "recommended": 0, "related": 0}}

    # 展开子标签：用 tree_number 前缀匹配（比 parent_id 更精确）
    # 先查选中标签的 tree_number，用 LIKE 匹配所有子节点
    tn_result = await db.execute(
        select(GlobalTagTreeNumber.tree_number).where(GlobalTagTreeNumber.tag_id.in_(ids))
    )
    tree_prefixes = [f"{row[0]}%" for row in tn_result]
    all_ids = set(ids)
    if tree_prefixes:
        children = await db.execute(
            select(GlobalTagTreeNumber.tag_id).where(
                or_(GlobalTagTreeNumber.tree_number.like(p) for p in tree_prefixes),
                ~GlobalTagTreeNumber.tag_id.in_(ids),
            )
        )
        for (cid,) in children:
            all_ids.add(cid)
    all_ids = list(all_ids)

    # 查询每篇文献匹配到的标签数
    count_q = select(
        GlobalLiteratureTag.literature_id,
        func.count(GlobalLiteratureTag.tag_id).label("matched"),
        func.sum(case((GlobalLiteratureTag.is_major == True, 1), else_=0)).label("major_cnt"),
    ).where(GlobalLiteratureTag.tag_id.in_(all_ids)).group_by(GlobalLiteratureTag.literature_id)

    rows = (await db.execute(count_q)).all()
    if not rows:
        return {"total_articles": 0, "daily_estimate": 0, "by_priority": {"must_read": 0, "recommended": 0, "related": 0}}

    total = len(all_ids)
    mode_priority = {"strict": 0, "standard": 1, "loose": 2}

    def classify(lit_total_matched: int, lit_major_cnt: int) -> str | None:
        """模拟 feed_engine 优先级分类，为 None 表示跳过"""
        if match_mode == "strict" and lit_total_matched < total:
            return None
        if match_mode == "standard":
            if lit_major_cnt >= 1:
                return "must_read" if lit_total_matched >= 3 else "recommended"
            elif lit_total_matched >= 2:
                return "recommended"
            else:
                return "related"
        elif match_mode == "loose":
            return "must_read" if lit_total_matched >= 3 else "recommended"
        else:
            # strict already filtered above
            return "must_read" if lit_major_cnt >= 2 else "recommended"

    by_priority: dict[str, int] = {"must_read": 0, "recommended": 0, "related": 0}
    for row in rows:
        p = classify(row.matched, row.major_cnt)
        if p:
            by_priority[p] += 1

    # DAILY_RATE ≈ 每日新文献中被议题覆盖的比例（基于经验值：日均 200 篇，约 15-30% 命中）
    # 用全库比例 × 日入库量估算
    total_articles = len(rows)
    daily_estimate = max(1, round(total_articles * 0.002))  # 0.2% of total pool ≈ 日均新增命中数
    if daily_estimate > 50:
        daily_estimate = 50  # cap 避免读数过夸张

    return {
        "total_articles": total_articles,
        "daily_estimate": daily_estimate,
        "by_priority": by_priority,
        "details": {
            "matched_tags": len(all_ids),
            "match_mode": match_mode,
            "mode_description": {
                "strict": "仅推送全部标签同时命中的文献（量少精准）",
                "standard": "优先推送主标签命中的高质量文献（量适中）",
                "loose": "任一标签命中即推送（量大全面）",
            }.get(match_mode, ""),
            "total_literature_in_db": (await db.execute(select(func.count(GlobalLiteratureTag.literature_id)).select_from(GlobalLiteratureTag))).scalar() or 0,
        }
    }


@router.post("/batch-subscribe", summary="批量订阅")
async def batch_subscribe(req: BatchSubscribe, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    """批量订阅标签 + 自动包含子标签（层级展开）+ 生成 Feed"""
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return {"status": "ok", "subscribed": 0, "feeds_generated": 0, "total_tags": 0, "note": "请重新登录后重试"}
    user_uuid, tid = uid(user), uuid.UUID(raw_tid)

    # 展开层级：如果 include_children，用 tree_number 前缀匹配
    all_tag_ids: set[uuid.UUID] = set()
    tn_to_expand = []
    for raw_id in req.tag_ids:
        tag_id = uuid.UUID(raw_id)
        all_tag_ids.add(tag_id)
        if req.include_children:
            tn_to_expand.append(tag_id)

    if tn_to_expand:
        tn_result = await db.execute(
            select(GlobalTagTreeNumber.tree_number).where(GlobalTagTreeNumber.tag_id.in_(tn_to_expand))
        )
        tree_prefixes = [f"{row[0]}%" for row in tn_result]
        if tree_prefixes:
            children = await db.execute(
                select(GlobalTagTreeNumber.tag_id).where(
                    or_(GlobalTagTreeNumber.tree_number.like(p) for p in tree_prefixes),
                    ~GlobalTagTreeNumber.tag_id.in_(all_tag_ids),
                )
            )
            for (cid,) in children:
                all_tag_ids.add(cid)

    tag_uuids = list(all_tag_ids)

    # 过滤掉未激活标签（避免用户选了不可用标签时报 400）
    tags_check = await db.execute(
        select(GlobalTag.id, GlobalTag.is_active).where(GlobalTag.id.in_(tag_uuids))
    )
    active_tag_ids = [
        tag_id for tag_id, is_active in tags_check if is_active
    ]

    # 批量查询现有订阅
    existing_result = await db.execute(
        select(UserSubscription).where(UserSubscription.user_id == user_uuid, UserSubscription.tag_id.in_(active_tag_ids))
    )
    existing_map = {row.tag_id: row for row in existing_result.scalars()}

    count = 0
    for tag_id in active_tag_ids:
        sub = existing_map.get(tag_id)
        if sub:
            sub.match_mode = req.match_mode
            sub.is_active = True
        else:
            db.add(UserSubscription(user_id=user_uuid, tenant_id=tid, tag_id=tag_id, match_mode=req.match_mode))
        count += 1
    await db.commit()

    # 批量生成 Feed（限制最近文献，避免超时）
    tagged_lit = await db.execute(
        select(GlobalLiteratureTag.literature_id).distinct()
        .where(GlobalLiteratureTag.tag_id.in_(tag_uuids))
        .limit(500)
    )
    lit_ids = [r for (r,) in tagged_lit]
    feeds = await batch_generate_feeds(db, lit_ids) if lit_ids else 0
    await db.commit()
    return {"status": "ok", "subscribed": count, "feeds_generated": feeds, "total_tags": len(tag_uuids)}


@router.post("/toggle", summary="切换订阅")
async def toggle_subscription(req: SubscriptionToggle, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return {"status": "skipped", "note": "请重新登录后重试"}
    user_uuid, tid = uid(user), uuid.UUID(raw_tid)
    try:
        tag_uuid = uuid.UUID(req.tag_id)
    except ValueError:
        raise HTTPException(400, detail="Invalid tag ID")
    sub = (await db.execute(select(UserSubscription).where(UserSubscription.user_id == user_uuid, UserSubscription.tag_id == tag_uuid))).scalar()
    if req.subscribe and not sub:
        db.add(UserSubscription(user_id=user_uuid, tenant_id=tid, tag_id=tag_uuid, match_mode=req.match_mode)); await db.commit()
        return {"status": "subscribed"}
    if not req.subscribe and sub: sub.is_active = False; await db.commit(); return {"status": "unsubscribed"}
    if sub and req.subscribe: sub.match_mode = req.match_mode; sub.is_active = True; await db.commit(); return {"status": "updated"}
    return {"status": "no_change"}


@router.post("/upgrade/preview", summary="升级预览")
async def preview_upgrade(target_plan: str = "pro", db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    current = await get_tenant_plan(db)
    target = PLANS.get(target_plan, PLANS["free"])
    gained = [{"feature": k, "from": str(current[k]), "to": str(target[k])} for k in target if target[k] != current[k]]
    return {"current_plan": current["name"], "target_plan": target["name"], "changes": gained,
            "prices": {"pro": "¥35/月", "team": "¥70/人/月"}}


@router.post("/upgrade", summary="升级方案")
async def upgrade_tenant(req: UpgradeRequest, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
    """升级方案 — 需先通过微信支付完成"""
    if req.target_plan not in PLANS:
        raise HTTPException(400, f"无效方案: {req.target_plan}")
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return {"status": "skipped", "note": "个人方案无需升级，加入团队后可升级方案"}
    t = await db.get(Tenant, uuid.UUID(raw_tid))
    if not t:
        raise HTTPException(404, "Tenant not found")

    return {"status": "payment_required", "message": "请先通过微信支付完成升级",
            "plan": req.target_plan, "amount_cny": PLAN_PRICES.get(req.target_plan, 3500) // 100}


# ─── 微信支付 ───

@router.post("/wechat-pay/create", summary="创建支付")
async def create_wechat_pay_order(
    plan: str = "pro",
    db: AsyncSession = Depends(get_db),
    user: dict = Depends(get_current_user),
    request: Request = None,
):
    """创建微信支付订单，返回二维码"""
    if plan not in PLAN_PRICES:
        raise HTTPException(400, f"不支持升级到 {plan}")
    raw_tid = tenant_ctx.get()
    if not raw_tid:
        return {"status": "skipped", "note": "个人方案无需升级，加入团队后可升级方案"}

    tenant_id = uuid.UUID(raw_tid)
    amount = PLAN_PRICES[plan]
    out_trade_no = generate_out_trade_no()
    client_ip = request.client.host if request else "127.0.0.1"

    order = PaymentOrder(
        id=uuid.uuid4(), tenant_id=tenant_id, user_id=uid(user),
        plan=plan, amount=amount, status="pending",
        out_trade_no=out_trade_no,
    )
    db.add(order)
    await db.commit()

    try:
        code_url, prepay_id = await create_unified_order(out_trade_no, plan, client_ip)
        order.code_url = code_url
        order.prepay_id = prepay_id
        await db.commit()
    except RuntimeError as e:
        order.status = "cancelled"
        await db.commit()
        raise HTTPException(502, detail=str(e))

    return {
        "order_id": str(order.id),
        "out_trade_no": out_trade_no,
        "code_url": code_url,
        "amount_cny": amount // 100,
        "plan": plan,
    }


@router.post("/wechat-pay/notify", summary="支付回调")
async def wechat_pay_notify(request: Request, db: AsyncSession = Depends(get_db)):
    """微信支付回调通知（无认证，微信服务器直接请求）"""
    xml_body = (await request.body()).decode("utf-8")
    result = verify_notify(xml_body)

    if not result.get("verified"):
        return fail_xml("verify_failed")

    out_trade_no = result.get("out_trade_no")
    transaction_id = result.get("transaction_id")

    order = (await db.execute(
        select(PaymentOrder).where(PaymentOrder.out_trade_no == out_trade_no)
    )).scalar()

    if not order or order.status == "paid":
        return success_xml()

    order.status = "paid"
    order.transaction_id = transaction_id
    order.pay_time = datetime.now(UTC)
    await db.commit()

    try:
        await _do_upgrade(db, order.tenant_id, "free", order.plan)
    except Exception:
        return fail_xml("upgrade_failed")

    return success_xml()


@router.get("/payment/{order_id}/status", summary="支付状态")
async def payment_status(
    order_id: str,
    db: AsyncSession = Depends(get_db),
    user: dict = Depends(get_current_user),
):
    """查询支付订单状态（前端轮询用）"""
    order = await db.get(PaymentOrder, uuid.UUID(order_id))
    if not order:
        raise HTTPException(404, "订单不存在")
    if order.user_id != uid(user):
        raise HTTPException(403, "无权查看此订单")

    return {
        "order_id": str(order.id),
        "status": order.status,
        "plan": order.plan,
        "amount_cny": order.amount // 100,
        "pay_time": order.pay_time.isoformat() if order.pay_time else None,
        "transaction_id": order.transaction_id,
    }


@router.post("/payment/{order_id}/mock-paid", summary="模拟支付")
async def mock_payment_callback(
    order_id: str,
    db: AsyncSession = Depends(get_db),
    user: dict = Depends(get_current_user),
):
    """开发模式：模拟支付成功回调（仅 dev 可用）"""
    if not is_dev_mode():
        raise HTTPException(403, "仅开发模式可用")

    order = await db.get(PaymentOrder, uuid.UUID(order_id))
    if not order:
        raise HTTPException(404, "订单不存在")
    if order.status != "pending":
        raise HTTPException(400, f"订单状态异常: {order.status}")

    order.status = "paid"
    order.transaction_id = f"mock_{uuid.uuid4().hex[:16]}"
    order.pay_time = datetime.now(UTC)
    await db.commit()

    result = await _do_upgrade(db, order.tenant_id, "free", order.plan)
    return {"status": "paid", "plan": result["plan"], "old_plan": result["old_plan"]}
