"""微信服务号网页授权:构造授权链接 + code 换 openid + 拉 userinfo。

用于 CPS 落地页(coupon.shaguabijia.com)在微信内拿用户身份。流程(见微信文档):
  授权链接(snsapi_base|snsapi_userinfo) → 回调带 code → sns/oauth2/access_token 换 openid
  → (userinfo 时)sns/userinfo 拿昵称头像。

注意:
  - 这里的 access_token 是【网页授权 token】,与基础 access_token(cgi-bin/token)不同,
    走 sns/* 接口,不需要 IP 白名单。
  - secret 只在服务器,不下发客户端。
  - 用 WX_MP_APPID/SECRET(已认证服务号),区别于 WECHAT_APP_ID(App 移动应用)。
"""
from __future__ import annotations

from urllib.parse import quote

import httpx

from app.core.config import settings

_API = "https://api.weixin.qq.com"
_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize"
_TIMEOUT = 10


class WxOauthError(Exception):
    """网页授权调用失败(凭证/网络/code 失效等)。调用方兜底为无 openid。"""


def build_authorize_url(redirect_uri: str, scope: str, state: str) -> str:
    """构造网页授权跳转链接。scope: 'snsapi_base'(静默,仅 openid) | 'snsapi_userinfo'(昵称头像)。

    微信要求 redirect_uri urlEncode、参数顺序固定、结尾 #wechat_redirect。
    """
    return (
        f"{_AUTHORIZE}?appid={settings.WX_MP_APPID}"
        f"&redirect_uri={quote(redirect_uri, safe='')}"
        f"&response_type=code&scope={scope}&state={quote(state, safe='')}"
        f"#wechat_redirect"
    )


def exchange_code(code: str) -> dict:
    """code 换网页授权 access_token + openid。返回含 openid/access_token/scope(/unionid)。

    code 5 分钟内一次性有效。失败(errcode)抛 WxOauthError。
    """
    resp = httpx.get(
        f"{_API}/sns/oauth2/access_token",
        params={
            "appid": settings.WX_MP_APPID,
            "secret": settings.WX_MP_SECRET,
            "code": code,
            "grant_type": "authorization_code",
        },
        timeout=_TIMEOUT,
    )
    data = resp.json()
    if "openid" not in data:
        raise WxOauthError(f"exchange_code failed: {data}")
    return data


def get_userinfo(access_token: str, openid: str) -> dict:
    """拉用户信息(nickname/headimgurl/unionid)。需 scope=snsapi_userinfo 的 access_token。"""
    resp = httpx.get(
        f"{_API}/sns/userinfo",
        params={"access_token": access_token, "openid": openid, "lang": "zh_CN"},
        timeout=_TIMEOUT,
    )
    resp.encoding = "utf-8"  # 昵称含中文/emoji,强制 utf-8 避免乱码
    data = resp.json()
    if "openid" not in data:
        raise WxOauthError(f"get_userinfo failed: {data}")
    return data
