"""
微信支付服务（APIv2）
=====================

提供 Native（扫码）支付能力：
1. 统一下单 - 获取二维码链接 code_url
2. 回调通知处理 - MD5 验签 + 更新订单 + 充值积分
3. 查询订单 - 兜底确认支付状态

设计原则：
- 不依赖外部 wechatpay SDK，轻量级自实现
- 所有网络请求使用 app.state.http_clients["default"]
- 回调验签使用 APIv2 的 MD5 签名机制

微信支付 APIv2 文档：
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
"""

from __future__ import annotations

import hashlib
import logging
import uuid
from typing import Any

import httpx

from app.config import get_settings

logger = logging.getLogger(__name__)

# ── 常量 ──────────────────────────────────────────────

WXPAY_BASE_URL = "https://api.mch.weixin.qq.com"
WXPAY_UNIFIEDORDER_ENDPOINT = "/pay/unifiedorder"
WXPAY_ORDERQUERY_ENDPOINT = "/pay/orderquery"


class WechatPayError(Exception):
    """微信支付调用异常"""

    def __init__(self, message: str, *, code: str | None = None, detail: dict | None = None):
        super().__init__(message)
        self.code = code
        self.detail = detail or {}


class WechatPayService:
    """
    微信支付服务封装（APIv2）

    使用示例:
        service = WechatPayService()
        result = await service.native_order(
            client=client,
            description="积分充值",
            out_trade_no="ORDER_123456",
            amount=100,  # 单位：分
        )
        # result["code_url"] 为二维码链接
    """

    def __init__(self):
        settings = get_settings()

        self.mchid = settings.WXPAY_MCHID
        self.appid = settings.WXPAY_APPID
        self.api_key = settings.WXPAY_API_KEY
        self.notify_url = settings.WXPAY_NOTIFY_URL

        if not all([self.mchid, self.appid, self.api_key]):
            raise WechatPayError("微信支付配置不完整：WXPAY_MCHID / WXPAY_APPID / WXPAY_API_KEY 未配置")

    # ── 签名与验签 ──────────────────────────────────────

    def _sign(self, params: dict[str, Any]) -> str:
        """
        微信支付 APIv2 MD5 签名。

        规则：
        1. 过滤值为空的参数和 sign 字段
        2. 按键名 ASCII 升序排序
        3. 拼接成 key1=value1&key2=value2 格式
        4. 末尾拼接 &key=API_KEY
        5. MD5 加密，结果转大写
        """
        # 过滤空值和 sign
        filtered = {k: str(v) for k, v in params.items() if v is not None and v != "" and k != "sign"}
        # ASCII 升序排序
        sorted_items = sorted(filtered.items(), key=lambda x: x[0])
        # 拼接字符串
        sign_str = "&".join(f"{k}={v}" for k, v in sorted_items)
        # 拼接 key
        sign_str += f"&key={self.api_key}"
        # MD5 大写（微信支付 APIv2 签名算法强制使用 MD5）
        return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()  # nosec: B324

    def verify_notify(self, notify_data: dict[str, Any]) -> bool:
        """
        验证微信支付回调签名（APIv2）。

        :param notify_data: 微信回调的 XML 解析后的字典，含 sign 字段
        :return: 是否验证通过
        """
        sign = notify_data.get("sign", "")
        if not sign:
            return False

        # 复制数据，移除 sign 字段
        data = {k: v for k, v in notify_data.items() if k != "sign"}
        calculated = self._sign(data)
        return sign == calculated

    # ── XML 工具 ────────────────────────────────────────

    @staticmethod
    def _dict_to_xml(data: dict[str, Any]) -> str:
        """将字典转为微信 XML 格式"""
        xml = "<xml>"
        for key, value in data.items():
            xml += f"<{key}><![CDATA[{value}]]></{key}>"
        xml += "</xml>"
        return xml

    @staticmethod
    def _xml_to_dict(xml_str: str) -> dict[str, Any]:
        """将微信 XML 转为字典"""
        import xml.etree.ElementTree as ET  # nosec: B405

        root = ET.fromstring(xml_str)  # nosec: B314
        result = {}
        for child in root:
            result[child.tag] = child.text or ""
        return result

    # ── 网络请求 ────────────────────────────────────────

    async def _request(
        self,
        client: httpx.AsyncClient,
        endpoint: str,
        params: dict[str, Any],
    ) -> dict:
        """发送带签名的微信支付 XML 请求"""
        if not self.api_key:
            raise WechatPayError("微信支付未配置，无法发起请求")

        # 添加必要字段
        params["mch_id"] = self.mchid
        params["nonce_str"] = uuid.uuid4().hex[:32]
        # 签名
        params["sign"] = self._sign(params)

        xml_body = self._dict_to_xml(params)
        full_url = f"{WXPAY_BASE_URL}{endpoint}"

        logger.debug(f"[WechatPay] POST {endpoint} body={xml_body[:200]}")

        response = await client.post(
            full_url,
            content=xml_body.encode("utf-8"),
            headers={"Content-Type": "application/xml"},
            timeout=httpx.Timeout(30.0, connect=10.0),
        )

        try:
            data = self._xml_to_dict(response.text)
        except Exception as e:
            raise WechatPayError(f"XML 解析失败: {e}", detail={"raw": response.text})

        return_code = data.get("return_code", "")
        return_msg = data.get("return_msg", "")

        if return_code != "SUCCESS":
            raise WechatPayError(
                message=f"微信支付通信失败: {return_msg}",
                code=return_code,
                detail=data,
            )

        result_code = data.get("result_code", "")
        if result_code != "SUCCESS":
            err_code = data.get("err_code", "")
            err_code_des = data.get("err_code_des", "")
            raise WechatPayError(
                message=f"微信支付业务失败: {err_code_des} ({err_code})",
                code=err_code,
                detail=data,
            )

        # API 返回成功，再验签一次返回数据
        resp_sign = data.get("sign", "")
        if resp_sign:
            expected = self._sign({k: v for k, v in data.items() if k != "sign"})
            if resp_sign != expected:
                logger.warning("[WechatPay] 返回数据签名校验失败")

        return data

    # ── 业务接口 ────────────────────────────────────────

    async def native_order(
        self,
        client: httpx.AsyncClient,
        *,
        description: str,
        out_trade_no: str,
        amount: int,
        attach: str | None = None,
        time_expire: str | None = None,
        client_ip: str | None = None,
    ) -> dict:
        """
        Native 支付 - 统一下单

        :param description: 商品描述
        :param out_trade_no: 商户订单号
        :param amount: 订单金额（单位：分）
        :param attach: 附加数据（回调原样返回）
        :param time_expire: 订单失效时间，格式：yyyyMMddHHmmss，如 20240101120000
        :param client_ip: 客户端真实 IP（优先从 Nginx X-Forwarded-For 获取）
        :return: 含 code_url 的字典
        """
        # 优先使用传入的真实 IP，兜底 127.0.0.1
        spbill_ip = client_ip if client_ip else "127.0.0.1"
        params = {
            "appid": self.appid,
            "body": description,
            "out_trade_no": out_trade_no,
            "total_fee": amount,
            "spbill_create_ip": spbill_ip,
            "notify_url": self.notify_url,
            "trade_type": "NATIVE",
        }
        if attach:
            params["attach"] = attach
        if time_expire:
            params["time_expire"] = time_expire

        return await self._request(client, WXPAY_UNIFIEDORDER_ENDPOINT, params)

    async def query_order(
        self,
        client: httpx.AsyncClient,
        *,
        out_trade_no: str,
    ) -> dict:
        """
        查询订单状态

        :param out_trade_no: 商户订单号
        :return: 订单详情，含 trade_state
        """
        params = {
            "appid": self.appid,
            "out_trade_no": out_trade_no,
        }
        return await self._request(client, WXPAY_ORDERQUERY_ENDPOINT, params)


# ── 便捷函数 ──────────────────────────────────────────


def get_wxpay_service() -> WechatPayService:
    """获取微信支付服务实例"""
    return WechatPayService()
