# -*- coding: utf-8 -*-
"""
微信认证服务 —— 封装 code2Session API 调用。

通过 httpx.AsyncClient 异步调用微信 jscode2session 接口，
将小程序端的临时登录凭证 (code) 换取 openid / session_key。
"""

from __future__ import annotations

import logging

import httpx

from app.config import WX_APPID, WX_SECRET
from app.trace.decorators import trace_service

logger = logging.getLogger(__name__)

CODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"

# 微信 errcode → (HTTP 状态码, 用户可见提示)
_WX_ERROR_MAP: dict[int, tuple[int, str]] = {
    40029: (401, "登录凭证无效，请重新登录"),
    45011: (429, "请求过于频繁"),
    40226: (403, "账号存在风险"),
}


class WeChatAuthError(Exception):
    """微信认证错误，包含 errcode 和 errmsg。"""

    def __init__(self, errcode: int, errmsg: str) -> None:
        self.errcode = errcode
        self.errmsg = errmsg
        super().__init__(f"WeChatAuthError({errcode}): {errmsg}")

    @property
    def http_status(self) -> int:
        """根据 errcode 映射到建议的 HTTP 状态码。"""
        return _WX_ERROR_MAP.get(self.errcode, (401, ""))[0]

    @property
    def detail(self) -> str:
        """根据 errcode 映射到用户可见的错误提示。"""
        return _WX_ERROR_MAP.get(self.errcode, (401, "微信登录失败"))[1]


@trace_service(description_zh="微信登录code换session", description_en="WeChat code to session")
async def code2session(code: str) -> dict:
    """
    调用微信 code2Session 接口。

    参数:
        code: 小程序端 wx.login() 获取的临时登录凭证

    返回:
        {"openid": str, "session_key": str, "unionid": str | None}

    异常:
        WeChatAuthError: 微信接口返回非零 errcode 时抛出
        RuntimeError: WX_APPID / WX_SECRET 环境变量缺失时抛出
    """
    appid = WX_APPID
    secret = WX_SECRET

    if not appid or not secret:
        raise RuntimeError("微信配置缺失：WX_APPID 或 WX_SECRET 未设置")

    params = {
        "appid": appid,
        "secret": secret,
        "js_code": code,
        "grant_type": "authorization_code",
    }

    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.get(CODE2SESSION_URL, params=params)
        resp.raise_for_status()
        data = resp.json()

    errcode = data.get("errcode", 0)
    if errcode != 0:
        errmsg = data.get("errmsg", "unknown error")
        logger.warning("微信 code2Session 失败: errcode=%s, errmsg=%s", errcode, errmsg)
        raise WeChatAuthError(errcode, errmsg)

    return {
        "openid": data["openid"],
        "session_key": data["session_key"],
        "unionid": data.get("unionid"),
    }
