"""易支付对接服务层。

职责：
- 生成签名 / 验签
- 构造发起支付所需参数
- 调用 mapi 接口获取支付 URL
- 验证异步回调签名
"""
from decimal import ROUND_HALF_UP, Decimal
import hashlib
import random
import string
import time
from datetime import datetime
from typing import Any

import httpx

from app.config import settings


# ── 签名 ──────────────────────────────────────────────────────────────────────

def _sign(params: dict[str, Any], key: str) -> str:
    """按易支付 MD5 签名规则生成 sign。"""
    filtered = {
        k: v for k, v in params.items()
        if v is not None and v != "" and k not in ("sign", "sign_type")
    }
    sorted_pairs = sorted(filtered.items(), key=lambda x: x[0])
    query = "&".join(f"{k}={v}" for k, v in sorted_pairs)
    raw = query + key
    return hashlib.md5(raw.encode("utf-8")).hexdigest().lower()


def verify_sign(params: dict[str, Any], key: str) -> bool:
    """验证回调签名。"""
    expected = _sign(params, key)
    return params.get("sign", "") == expected


# ── 订单号生成 ─────────────────────────────────────────────────────────────────

def gen_out_trade_no() -> str:
    """生成商户唯一订单号：RC + 时间戳毫秒 + 6位随机字母数字。"""
    ts = int(time.time() * 1000)
    rand = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
    return f"RC{ts}{rand}"


# ── 发起支付（mapi 后端接口）──────────────────────────────────────────────────

async def create_payment(
    out_trade_no: str,
    amount_yuan: str,          # 字符串，保留两位小数
    name: str,
    client_ip: str,
    notify_url: str | None = None,
    return_url: str | None = None,
    param: str = "",
) -> dict[str, Any]:
    """
    调用易支付 mapi 接口，返回原始 JSON。
    调用方根据返回的 payurl / qrcode / urlscheme 决定跳转方式。
    """
    notify = notify_url or settings.EPAY_NOTIFY_URL
    ret = return_url or settings.EPAY_RETURN_URL

    params: dict[str, Any] = {
        "pid": settings.EPAY_PID,
        "type": "alipay",
        "out_trade_no": out_trade_no,
        "notify_url": notify,
        "return_url": ret,
        "name": name[:127],
        "money": amount_yuan,
        "clientip": client_ip,
        "device": "pc",
    }
    if param:
        params["param"] = param

    params["sign"] = _sign(params, settings.EPAY_KEY)
    params["sign_type"] = "MD5"

    async with httpx.AsyncClient(timeout=15) as client:
        resp = await client.post(
            f"{settings.EPAY_API_URL}/xpay/epay/mapi.php",
            data=params,
        )
        resp.raise_for_status()
        data = resp.json()
        # 若 mapi 未返回 qrcode 字段，用 trade_no 构造收银台 URL 作为二维码内容
        if data.get("code") == 1 and not data.get("qrcode") and data.get("trade_no"):
            host = settings.EPAY_API_URL.rstrip("/").replace("http://", "https://")
            # 取域名部分，去掉路径
            from urllib.parse import urlparse
            parsed = urlparse(settings.EPAY_API_URL)
            cashier_host = f"https://{parsed.netloc}"
            data["qrcode"] = f"{cashier_host}/pay/{data['trade_no']}"
        return data


# ── 查询订单（轮询用）────────────────────────────────────────────────────────

async def query_order(out_trade_no: str) -> dict[str, Any]:
    """查询单个订单状态，返回原始 JSON。"""
    url = (
        f"{settings.EPAY_API_URL}/xpay/epay/api.php"
        f"?act=order"
        f"&pid={settings.EPAY_PID}"
        f"&key={settings.EPAY_KEY}"
        f"&out_trade_no={out_trade_no}"
    )
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(url)
        resp.raise_for_status()
        return resp.json()


# ── 金额工具 ──────────────────────────────────────────────────────────────────

def yuan_to_cents(yuan: Decimal) -> int:
    """元转分，四舍五入，全程 Decimal 避免浮点误差。"""
    return int((yuan * 100).to_integral_value(rounding=ROUND_HALF_UP))


def cents_to_yuan_str(cents: int) -> str:
    """分转元字符串，保留两位小数。"""
    return str(Decimal(cents) / 100)


def format_amount(yuan: Decimal) -> str:
    """格式化为易支付要求的金额字符串（两位小数）。"""
    return str(yuan.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
