"""微信登录 Store 路由

挂载在 /store/auth/wechat 前缀下：
  POST /miniapp         小程序 code 换 JWT
  GET  /mp              发起公众号 OAuth 跳转
  GET  /mp/callback     公众号 OAuth 回调
  GET  /open            发起开放平台扫码跳转
  GET  /open/callback   开放平台扫码回调
"""

import logging
import uuid
from typing import Optional
from urllib.parse import urlencode, quote

import httpx
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import RedirectResponse
from pydantic import BaseModel, Field
from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db
from app.config import settings as s
from app.core.models.customer import Customer
from app.core.models.tenant_settings import TenantSettings
from app.core.cache import cache_get, cache_set
from app.core.services.plugin_helper import is_plugin_active
from app.api.routers.store.auth import _issue_token, _customer_to_dict

logger = logging.getLogger("uvicorn.error")

router = APIRouter(prefix="/store/auth/wechat", tags=["微信登录"])

# ── 工具函数 ──────────────────────────────────────────────────────────────────

async def _get_wechat_config(db: AsyncSession, tenant_id: int) -> dict:
    """从 TenantSettings.extra.wechat_auth 读取配置"""
    if not await is_plugin_active("wechat_auth", db, tenant_id):
        return {}
    r = await db.execute(
        select(TenantSettings).where(TenantSettings.tenant_id == tenant_id)
    )
    ts = r.scalar_one_or_none()
    cfg = ((ts.extra or {}) if ts else {}).get("wechat_auth", {})
    if not cfg.get("enabled"):
        return {}
    return cfg


async def _find_binding(db: AsyncSession, tenant_id: int, platform: str, openid: str):
    """根据 openid 查找已绑定的客户"""
    r = await db.execute(
        text(
            "SELECT customer_id FROM wechat_bindings "
            "WHERE tenant_id=:tid AND platform=:platform AND openid=:openid LIMIT 1"
        ),
        {"tid": tenant_id, "platform": platform, "openid": openid},
    )
    row = r.fetchone()
    return row[0] if row else None


async def _save_binding(
    db: AsyncSession,
    tenant_id: int,
    customer_id: int,
    platform: str,
    openid: str,
    unionid: Optional[str] = None,
    nickname: Optional[str] = None,
    avatar: Optional[str] = None,
) -> None:
    """插入或更新微信绑定记录"""
    await db.execute(
        text(
            "INSERT INTO wechat_bindings (tenant_id, customer_id, platform, openid, unionid, nickname, avatar) "
            "VALUES (:tid, :cid, :platform, :openid, :unionid, :nickname, :avatar) "
            "ON DUPLICATE KEY UPDATE "
            "customer_id=VALUES(customer_id), unionid=VALUES(unionid), "
            "nickname=VALUES(nickname), avatar=VALUES(avatar)"
        ),
        {
            "tid": tenant_id,
            "cid": customer_id,
            "platform": platform,
            "openid": openid,
            "unionid": unionid,
            "nickname": nickname,
            "avatar": avatar,
        },
    )
    await db.commit()


async def _get_or_create_customer(
    db: AsyncSession,
    tenant_id: int,
    openid: str,
    platform: str,
    nickname: Optional[str] = None,
    avatar: Optional[str] = None,
    unionid: Optional[str] = None,
) -> Customer:
    """找到已绑定客户，或创建新客户并绑定"""
    existing_id = await _find_binding(db, tenant_id, platform, openid)
    if existing_id:
        r = await db.execute(select(Customer).where(Customer.id == existing_id))
        customer = r.scalar_one_or_none()
        if customer:
            return customer

    # 若有 unionid，也尝试通过 unionid 找到其他平台已绑定的同一用户
    if unionid:
        r = await db.execute(
            text(
                "SELECT customer_id FROM wechat_bindings "
                "WHERE tenant_id=:tid AND unionid=:unionid LIMIT 1"
            ),
            {"tid": tenant_id, "unionid": unionid},
        )
        row = r.fetchone()
        if row:
            r2 = await db.execute(select(Customer).where(Customer.id == row[0]))
            customer = r2.scalar_one_or_none()
            if customer:
                await _save_binding(db, tenant_id, customer.id, platform, openid, unionid, nickname, avatar)
                return customer

    # 创建新客户（使用微信昵称）
    display_name = nickname or f"微信用户{openid[-6:]}"
    # 微信登录的用户没有邮箱，用 openid 生成一个内部占位邮箱
    fake_email = f"wx_{platform}_{openid}@wechat.internal"

    customer = Customer(
        tenant_id=tenant_id,
        name=display_name,
        email=fake_email,
        extra_data={"wechat_avatar": avatar, "source": "wechat"},
    )
    db.add(customer)
    await db.flush()

    await _save_binding(db, tenant_id, customer.id, platform, openid, unionid, nickname, avatar)
    await db.commit()
    await db.refresh(customer)
    return customer


def _tenant_id() -> int:
    return s.DEFAULT_TENANT_ID


@router.get("/config", summary="获取微信登录启用状态")
async def public_config(db: AsyncSession = Depends(get_db)):
    tid = _tenant_id()
    cfg = await _get_wechat_config(db, tid)
    return {
        "enabled": bool(cfg),
        "mp_enabled": bool(cfg.get("mp_appid") and cfg.get("mp_secret")),
        "open_enabled": bool(cfg.get("open_appid") and cfg.get("open_secret")),
        "miniapp_enabled": bool(cfg.get("miniapp_appid") and cfg.get("miniapp_secret")),
    }


# ── 1. 小程序登录 ──────────────────────────────────────────────────────────────

class MiniappLoginIn(BaseModel):
    code:      str  = Field(..., description="wx.login() 返回的 code")
    name:      Optional[str] = Field(None, description="昵称（可选，授权后传入）")
    avatar:    Optional[str] = Field(None, description="头像 URL（可选）")
    tenant_id: Optional[int] = None


@router.post("/miniapp", summary="小程序 code 换 JWT")
async def miniapp_login(body: MiniappLoginIn, db: AsyncSession = Depends(get_db)):
    tid = body.tenant_id or _tenant_id()
    cfg = await _get_wechat_config(db, tid)

    appid  = cfg.get("miniapp_appid", "")
    secret = cfg.get("miniapp_secret", "")
    if not appid or not secret:
        raise HTTPException(status_code=503, detail="小程序微信登录未配置，请联系商家")

    # 调用微信 jscode2session
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(
            "https://api.weixin.qq.com/sns/jscode2session",
            params={
                "appid":      appid,
                "secret":     secret,
                "js_code":    body.code,
                "grant_type": "authorization_code",
            },
        )
    data = resp.json()
    if "errcode" in data and data["errcode"] != 0:
        logger.error("WeChat jscode2session error: %s", data)
        raise HTTPException(status_code=400, detail=f"微信登录失败：{data.get('errmsg', '未知错误')}")

    openid  = data.get("openid", "")
    unionid = data.get("unionid")
    if not openid:
        raise HTTPException(status_code=400, detail="微信返回 openid 为空")

    customer = await _get_or_create_customer(
        db, tid, openid, "miniapp",
        nickname=body.name,
        avatar=body.avatar,
        unionid=unionid,
    )

    return {
        "access_token": _issue_token(customer.id),
        "token_type":   "bearer",
        "user":         _customer_to_dict(customer),
    }


# ── 2. 公众号 OAuth 发起跳转 ───────────────────────────────────────────────────

@router.get("/mp", summary="发起公众号 OAuth 授权")
async def mp_auth_start(
    redirect: str = Query("", description="登录成功后跳转的前端路径，如 /account"),
    db: AsyncSession = Depends(get_db),
):
    tid = _tenant_id()
    cfg = await _get_wechat_config(db, tid)
    appid = cfg.get("mp_appid", "")
    if not appid:
        raise HTTPException(status_code=503, detail="公众号微信登录未配置")

    api_base   = s.API_BASE_URL.rstrip("/")
    state      = f"{uuid.uuid4().hex}:{redirect or '/account'}"

    callback_uri = quote(f"{api_base}/api/store/auth/wechat/mp/callback", safe="")
    url = (
        f"https://open.weixin.qq.com/connect/oauth2/authorize"
        f"?appid={appid}"
        f"&redirect_uri={callback_uri}"
        f"&response_type=code"
        f"&scope=snsapi_userinfo"
        f"&state={state}"
        f"#wechat_redirect"
    )
    return RedirectResponse(url, status_code=302)


# ── 3. 公众号 OAuth 回调 ───────────────────────────────────────────────────────

@router.get("/mp/callback", summary="公众号 OAuth 回调")
async def mp_callback(
    code:  str = Query(...),
    state: str = Query(""),
    db:    AsyncSession = Depends(get_db),
):
    frontend_base = s.FRONTEND_URL.rstrip("/")
    tid = _tenant_id()
    cfg = await _get_wechat_config(db, tid)
    appid  = cfg.get("mp_appid", "")
    secret = cfg.get("mp_secret", "")

    # 解析 state 中携带的 redirect 路径
    redirect_path = "/account"
    if ":" in state:
        _, redirect_path = state.split(":", 1)

    if not appid or not secret:
        return RedirectResponse(f"{frontend_base}{redirect_path}?wechat_error=config")

    # 换取 access_token + openid
    async with httpx.AsyncClient(timeout=10) as client:
        r = await client.get(
            "https://api.weixin.qq.com/sns/oauth2/access_token",
            params={
                "appid":      appid,
                "secret":     secret,
                "code":       code,
                "grant_type": "authorization_code",
            },
        )
    token_data = r.json()
    openid       = token_data.get("openid", "")
    access_token = token_data.get("access_token", "")
    unionid      = token_data.get("unionid")

    if not openid:
        logger.error("WeChat mp callback error: %s", token_data)
        return RedirectResponse(f"{frontend_base}{redirect_path}?wechat_error=oauth")

    # 获取用户信息（头像、昵称）
    nickname = avatar = None
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            ui = await client.get(
                "https://api.weixin.qq.com/sns/userinfo",
                params={"access_token": access_token, "openid": openid, "lang": "zh_CN"},
            )
        ui_data  = ui.json()
        nickname = ui_data.get("nickname")
        avatar   = ui_data.get("headimgurl")
        if not unionid:
            unionid = ui_data.get("unionid")
    except Exception:
        pass

    customer = await _get_or_create_customer(
        db, tid, openid, "mp",
        nickname=nickname, avatar=avatar, unionid=unionid,
    )

    token = _issue_token(customer.id)
    return RedirectResponse(
        f"{frontend_base}/auth/wechat-callback?token={token}&redirect={quote(redirect_path, safe='')}",
        status_code=302,
    )


# ── 4. 开放平台扫码发起 ────────────────────────────────────────────────────────

@router.get("/open", summary="发起开放平台扫码登录")
async def open_qr_start(
    redirect: str = Query("", description="登录后跳转路径"),
    db: AsyncSession = Depends(get_db),
):
    tid = _tenant_id()
    cfg = await _get_wechat_config(db, tid)
    appid = cfg.get("open_appid", "")
    if not appid:
        raise HTTPException(status_code=503, detail="开放平台扫码登录未配置")

    api_base = s.API_BASE_URL.rstrip("/")
    state    = f"{uuid.uuid4().hex}:{redirect or '/account'}"

    callback_uri = quote(f"{api_base}/api/store/auth/wechat/open/callback", safe="")
    url = (
        f"https://open.weixin.qq.com/connect/qrconnect"
        f"?appid={appid}"
        f"&redirect_uri={callback_uri}"
        f"&response_type=code"
        f"&scope=snsapi_login"
        f"&state={state}"
        f"#wechat_redirect"
    )
    return RedirectResponse(url, status_code=302)


# ── 5. 开放平台扫码回调 ────────────────────────────────────────────────────────

@router.get("/open/callback", summary="开放平台扫码回调")
async def open_callback(
    code:  str = Query(...),
    state: str = Query(""),
    db:    AsyncSession = Depends(get_db),
):
    frontend_base = s.FRONTEND_URL.rstrip("/")
    tid = _tenant_id()
    cfg = await _get_wechat_config(db, tid)
    appid  = cfg.get("open_appid", "")
    secret = cfg.get("open_secret", "")

    redirect_path = "/account"
    if ":" in state:
        _, redirect_path = state.split(":", 1)

    if not appid or not secret:
        return RedirectResponse(f"{frontend_base}{redirect_path}?wechat_error=config")

    # 开放平台 access_token 换取（接口与公众号一致）
    async with httpx.AsyncClient(timeout=10) as client:
        r = await client.get(
            "https://api.weixin.qq.com/sns/oauth2/access_token",
            params={
                "appid":      appid,
                "secret":     secret,
                "code":       code,
                "grant_type": "authorization_code",
            },
        )
    token_data   = r.json()
    openid       = token_data.get("openid", "")
    access_token = token_data.get("access_token", "")
    unionid      = token_data.get("unionid")

    if not openid:
        logger.error("WeChat open callback error: %s", token_data)
        return RedirectResponse(f"{frontend_base}{redirect_path}?wechat_error=oauth")

    # 获取用户信息
    nickname = avatar = None
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            ui = await client.get(
                "https://api.weixin.qq.com/sns/userinfo",
                params={"access_token": access_token, "openid": openid, "lang": "zh_CN"},
            )
        ui_data  = ui.json()
        nickname = ui_data.get("nickname")
        avatar   = ui_data.get("headimgurl")
        if not unionid:
            unionid = ui_data.get("unionid")
    except Exception:
        pass

    customer = await _get_or_create_customer(
        db, tid, openid, "open",
        nickname=nickname, avatar=avatar, unionid=unionid,
    )

    token = _issue_token(customer.id)
    return RedirectResponse(
        f"{frontend_base}/auth/wechat-callback?token={token}&redirect={quote(redirect_path, safe='')}",
        status_code=302,
    )
