"""微信小程序登录 API"""
import secrets
from datetime import datetime, timedelta

import httpx
import jwt
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, get_tenant_by_appid
from app.config import settings
from app.core.cache import cache_get, cache_set
from app.core.models.customer import Customer
from app.core.models.customer_oauth import CustomerOAuthAccount
from app.core.models.tenant_channel import TenantChannel

router = APIRouter(tags=["小程序认证"])

_WX_CODE2SESSION = "https://api.weixin.qq.com/sns/jscode2session"
_ACCESS_TTL = timedelta(hours=2)
_REFRESH_TTL_SECONDS = 86400 * 30  # 30 天


async def _wx_code2session(appid: str, appsecret: str, code: str) -> dict:
    """向微信换取 openid/session_key。"""
    async with httpx.AsyncClient(timeout=10) as client:
        r = await client.get(_WX_CODE2SESSION, params={
            "appid": appid, "secret": appsecret,
            "js_code": code, "grant_type": "authorization_code",
        })
    try:
        data = r.json()
    except Exception:
        raise HTTPException(status_code=502, detail="微信服务响应异常")
    if "errcode" in data and data["errcode"] != 0:
        raise HTTPException(status_code=400, detail=f"微信登录失败: {data.get('errmsg')}")
    return data


def _issue_mp_access_token(customer_id: int, tenant_id: int, appid: str, openid: str) -> str:
    payload = {
        "sub": str(customer_id),
        "role": "customer",
        "tenant_id": tenant_id,
        "channel": "wechat_miniapp",
        "appid": appid,
        "openid": openid,
        "exp": datetime.utcnow() + _ACCESS_TTL,
    }
    return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")


async def _issue_refresh_token(customer_id: int, tenant_id: int, appid: str) -> str:
    token = secrets.token_urlsafe(32)
    await cache_set(f"mp:refresh:{token}", {
        "customer_id": customer_id,
        "tenant_id": tenant_id,
        "appid": appid,
    }, ttl=_REFRESH_TTL_SECONDS)
    return token


class MpLoginIn(BaseModel):
    code: str
    appid: str


class MpRefreshIn(BaseModel):
    refresh_token: str


@router.post("/store/mp/login", summary="微信小程序登录")
async def mp_login(body: MpLoginIn, db: AsyncSession = Depends(get_db)):
    # 1. AppID → 租户
    tid = await get_tenant_by_appid(body.appid, db)

    # 2. 取该租户的 appsecret
    ch_r = await db.execute(
        select(TenantChannel).where(TenantChannel.appid == body.appid)
    )
    ch: TenantChannel = ch_r.scalar_one_or_none()
    if not ch:
        raise HTTPException(status_code=404, detail="AppID 未注册")
    appsecret = ch.get_secret("appsecret")
    if not appsecret:
        raise HTTPException(status_code=500, detail="小程序密钥未配置")

    # 3. 微信 code → openid
    wx_data = await _wx_code2session(body.appid, appsecret, body.code)
    openid: str | None = wx_data.get("openid")
    if not openid:
        raise HTTPException(status_code=502, detail="微信未返回 openid")
    unionid: str | None = wx_data.get("unionid")

    # 4. 查 or 创建 customer
    oauth_r = await db.execute(
        select(CustomerOAuthAccount).where(
            CustomerOAuthAccount.tenant_id == tid,
            CustomerOAuthAccount.provider == "wechat_miniapp",
            CustomerOAuthAccount.appid == body.appid,
            CustomerOAuthAccount.openid == openid,
        )
    )
    oauth = oauth_r.scalar_one_or_none()

    if oauth:
        customer_id = oauth.customer_id
        if unionid and not oauth.unionid:
            oauth.unionid = unionid
            await db.commit()
    else:
        # email 字段非空，微信用户用空字符串占位
        customer = Customer(
            tenant_id=tid,
            name="微信用户",
            email="",
            phone=None,
            extra_data={},
        )
        db.add(customer)
        await db.flush()

        oauth = CustomerOAuthAccount(
            tenant_id=tid,
            customer_id=customer.id,
            provider="wechat_miniapp",
            appid=body.appid,
            openid=openid,
            unionid=unionid,
        )
        db.add(oauth)
        await db.commit()
        customer_id = customer.id

    # 5. 签发 token
    access_token = _issue_mp_access_token(customer_id, tid, body.appid, openid)
    refresh_token = await _issue_refresh_token(customer_id, tid, body.appid)

    return {
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer",
        "customer_id": customer_id,
    }


@router.post("/store/mp/token/refresh", summary="刷新 access token")
async def mp_refresh(body: MpRefreshIn, db: AsyncSession = Depends(get_db)):
    data = await cache_get(f"mp:refresh:{body.refresh_token}")
    if not data or not isinstance(data, dict):
        raise HTTPException(status_code=401, detail="refresh token 无效或已过期")

    customer_id = int(data["customer_id"])
    tid = int(data["tenant_id"])
    appid = data["appid"]

    oauth_r = await db.execute(
        select(CustomerOAuthAccount).where(
            CustomerOAuthAccount.customer_id == customer_id,
            CustomerOAuthAccount.provider == "wechat_miniapp",
            CustomerOAuthAccount.appid == appid,
        )
    )
    oauth = oauth_r.scalar_one_or_none()
    if not oauth:
        raise HTTPException(status_code=401, detail="账户不存在")

    access_token = _issue_mp_access_token(customer_id, tid, appid, oauth.openid)
    return {"access_token": access_token, "token_type": "bearer"}
