"""微信小程序原生支付（JSAPI miniapp pay v3）

配置存于 TenantSettings.extra["wechat_auth"]：
  miniapp_appid   — 小程序 AppID（已有）
  wxpay_mch_id    — 商户号
  wxpay_api_v3_key — API v3 密钥（32字节）
  wxpay_serial_no  — 商户 API 证书序列号
  wxpay_private_key — 商户 API 私钥 PEM 文本
  wxpay_notify_url  — 支付回调地址（必须 HTTPS，已在微信商户后台配置）
"""
import base64
import json
import logging
import time
import uuid
from datetime import datetime
from typing import Optional

import httpx
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db
from app.api.routers.store.auth import get_current_customer
from app.core.models.customer import Customer
from app.core.models.customer_oauth import CustomerOAuthAccount
from app.core.models.order import Order
from app.core.models.plugin import PluginConfig
from app.core.models.tenant_settings import TenantSettings
from app.core.services.plugin_helper import is_plugin_active

logger = logging.getLogger(__name__)
router = APIRouter(tags=["微信支付"])

WX_PAY_BASE = "https://api.mch.weixin.qq.com"


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

async def _get_wxcfg(db: AsyncSession, tenant_id: int) -> dict:
    """从 TenantSettings.extra.wechat_auth 读取微信配置（含支付字段）"""
    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("wxpay_mch_id"):
        raise HTTPException(503, detail="微信支付未配置，请在插件中心完善商户号等信息")
    return cfg


def _load_private_key(pem: str):
    return serialization.load_pem_private_key(pem.encode(), password=None)


def _rsa_sign(message: str, pem: str) -> str:
    key = _load_private_key(pem)
    sig = key.sign(message.encode("utf-8"), asym_padding.PKCS1v15(), hashes.SHA256())
    return base64.b64encode(sig).decode()


def _wx_auth_header(method: str, url_path: str, body: str,
                    mch_id: str, serial_no: str, pem: str) -> str:
    ts = str(int(time.time()))
    nonce = uuid.uuid4().hex
    msg = f"{method}\n{url_path}\n{ts}\n{nonce}\n{body}\n"
    sig = _rsa_sign(msg, pem)
    return (
        f'WECHATPAY2-SHA256-RSA2048 mchid="{mch_id}",'
        f'nonce_str="{nonce}",timestamp="{ts}",'
        f'serial_no="{serial_no}",signature="{sig}"'
    )


def _jsapi_params(appid: str, prepay_id: str, pem: str) -> dict:
    ts = str(int(time.time()))
    nonce = uuid.uuid4().hex
    msg = f"{appid}\n{ts}\n{nonce}\nprepay_id={prepay_id}\n"
    pay_sign = _rsa_sign(msg, pem)
    return {
        "timeStamp": ts,
        "nonceStr": nonce,
        "package": f"prepay_id={prepay_id}",
        "signType": "RSA",
        "paySign": pay_sign,
    }


def _aes_decrypt(api_v3_key: str, nonce: str, ciphertext: str, associated_data: str) -> str:
    key = api_v3_key.encode("utf-8")
    aesgcm = AESGCM(key)
    ct = base64.b64decode(ciphertext)
    plain = aesgcm.decrypt(nonce.encode("utf-8"), ct, associated_data.encode("utf-8"))
    return plain.decode("utf-8")


# ── 端点 ──────────────────────────────────────────────────────────────────────

@router.post("/store/orders/{order_no}/wxpay-params", summary="获取微信小程序支付参数")
async def get_wxpay_params(
    order_no: str,
    customer: Customer = Depends(get_current_customer),
    db: AsyncSession = Depends(get_db),
):
    cfg = await _get_wxcfg(db, customer.tenant_id)
    appid       = cfg.get("miniapp_appid", "")
    mch_id      = cfg.get("wxpay_mch_id", "")
    api_v3_key  = cfg.get("wxpay_api_v3_key", "")
    serial_no   = cfg.get("wxpay_serial_no", "")
    pem         = cfg.get("wxpay_private_key", "")
    notify_url  = cfg.get("wxpay_notify_url", "")

    missing = [k for k, v in {
        "miniapp_appid": appid, "wxpay_mch_id": mch_id,
        "wxpay_api_v3_key": api_v3_key, "wxpay_serial_no": serial_no,
        "wxpay_private_key": pem, "wxpay_notify_url": notify_url,
    }.items() if not v]
    if missing:
        raise HTTPException(503, detail=f"微信支付配置不完整，缺少：{', '.join(missing)}")

    # 查订单
    r = await db.execute(
        select(Order).where(
            Order.order_no == order_no,
            Order.customer_id == customer.id,
            Order.tenant_id == customer.tenant_id,
        )
    )
    order = r.scalar_one_or_none()
    if not order:
        raise HTTPException(404, detail="订单不存在")
    if order.status != "pending":
        raise HTTPException(400, detail=f"订单状态为「{order.status}」，无法支付")

    # 获取 openid
    oa_r = await db.execute(
        select(CustomerOAuthAccount).where(
            CustomerOAuthAccount.customer_id == customer.id,
            CustomerOAuthAccount.tenant_id == customer.tenant_id,
            CustomerOAuthAccount.provider == "wechat_miniapp",
        )
    )
    oa = oa_r.scalar_one_or_none()
    if not oa:
        raise HTTPException(400, detail="未找到微信 OpenID，请重新登录")

    # 调用微信 v3 统一下单
    url_path = "/v3/pay/transactions/jsapi"
    amount_fen = max(1, int(float(order.grand_total) * 100))
    body_dict = {
        "appid": appid,
        "mchid": mch_id,
        "description": f"订单 {order_no}",
        "out_trade_no": order_no,
        "notify_url": notify_url,
        "amount": {"total": amount_fen, "currency": "CNY"},
        "payer": {"openid": oa.openid},
    }
    body_str = json.dumps(body_dict, ensure_ascii=False, separators=(",", ":"))
    auth = _wx_auth_header("POST", url_path, body_str, mch_id, serial_no, pem)

    async with httpx.AsyncClient(timeout=15) as client:
        resp = await client.post(
            WX_PAY_BASE + url_path,
            content=body_str.encode("utf-8"),
            headers={
                "Authorization": auth,
                "Content-Type": "application/json",
                "Accept": "application/json",
                "User-Agent": "LS/1.0",
            },
        )

    if resp.status_code != 200:
        logger.error("wxpay unified order error %s: %s", resp.status_code, resp.text)
        detail = resp.json().get("message", resp.text) if resp.headers.get("content-type", "").startswith("application/json") else resp.text
        raise HTTPException(502, detail=f"微信支付下单失败：{detail}")

    prepay_id = resp.json().get("prepay_id")
    if not prepay_id:
        raise HTTPException(502, detail="微信返回数据异常：缺少 prepay_id")

    return _jsapi_params(appid, prepay_id, pem)


def _verify_notify_signature(headers: dict, raw_body: bytes, cert_pubkey_pem: str) -> bool:
    """用微信平台公钥验证回调签名（Wechatpay-Signature / RSA-SHA256）"""
    try:
        from cryptography.hazmat.primitives.serialization import load_pem_public_key
        from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
        timestamp = headers.get("wechatpay-timestamp", "")
        nonce     = headers.get("wechatpay-nonce", "")
        signature = headers.get("wechatpay-signature", "")
        if not (timestamp and nonce and signature):
            return False
        message = f"{timestamp}\n{nonce}\n{raw_body.decode('utf-8')}\n".encode("utf-8")
        pub = load_pem_public_key(cert_pubkey_pem.encode())
        pub.verify(base64.b64decode(signature), message, PKCS1v15(), hashes.SHA256())
        return True
    except Exception:
        return False


@router.post("/store/wxpay/notify", summary="微信支付回调通知")
async def wxpay_notify(request: Request, db: AsyncSession = Depends(get_db)):
    """
    微信支付回调：验签 → 解密 → 核对 mchid/appid/金额 → 标 paid。
    需在微信商户后台将 notify_url 设为本端点的完整 HTTPS 地址。
    """
    raw = await request.body()
    try:
        data = json.loads(raw)
    except Exception:
        return {"code": "FAIL", "message": "invalid json"}

    resource   = data.get("resource", {})
    nonce      = resource.get("nonce", "")
    ciphertext = resource.get("ciphertext", "")
    associated = resource.get("associated_data", "")
    headers    = dict(request.headers)

    rows = (await db.execute(select(TenantSettings))).scalars().all()
    for ts in rows:
        cfg = (ts.extra or {}).get("wechat_auth", {})
        api_v3_key       = cfg.get("wxpay_api_v3_key", "")
        expected_mch_id  = cfg.get("wxpay_mch_id", "")
        expected_appid   = cfg.get("miniapp_appid", "")
        platform_cert    = cfg.get("wxpay_platform_cert", "")

        if not api_v3_key or len(api_v3_key) != 32:
            continue

        # ① 验签：生产环境强制，开发环境（DEBUG=True）允许跳过
        from app.config import settings as _s
        if not platform_cert:
            if not _s.DEBUG:
                logger.error("wxpay notify: wxpay_platform_cert not configured for tenant %s, rejecting in production", ts.tenant_id)
                return {"code": "FAIL", "message": "platform cert not configured"}
            # DEBUG 模式下跳过验签，仅凭解密 + 金额校验兜底
        else:
            if not _verify_notify_signature(headers, raw, platform_cert):
                logger.warning("wxpay notify signature invalid for tenant %s", ts.tenant_id)
                continue

        # ② 解密资源
        try:
            plain  = _aes_decrypt(api_v3_key, nonce, ciphertext, associated)
            result = json.loads(plain)
        except Exception:
            continue

        # ③ trade_state 必须是 SUCCESS
        if result.get("trade_state") != "SUCCESS":
            return {"code": "SUCCESS", "message": "OK"}

        # ④ 核对 mchid / appid
        if result.get("mchid") != expected_mch_id or result.get("appid") != expected_appid:
            logger.error(
                "wxpay notify mchid/appid mismatch: got mchid=%s appid=%s, expected mchid=%s appid=%s",
                result.get("mchid"), result.get("appid"), expected_mch_id, expected_appid,
            )
            return {"code": "FAIL", "message": "mchid or appid mismatch"}

        order_no = result.get("out_trade_no", "")

        # ⑤ 查订单时限定 tenant_id，防跨租户
        r = await db.execute(
            select(Order).where(
                Order.order_no == order_no,
                Order.tenant_id == ts.tenant_id,
            ).with_for_update()
        )
        order = r.scalar_one_or_none()
        if not order:
            logger.error("wxpay notify: order %s not found for tenant %s", order_no, ts.tenant_id)
            return {"code": "FAIL", "message": "order not found"}

        # ⑥ 核对金额（微信回调单位：分）
        notify_fen   = result.get("amount", {}).get("total", 0)
        expected_fen = max(1, int(float(order.grand_total) * 100))
        if abs(notify_fen - expected_fen) > 1:   # 允许 1 分误差（浮点取整）
            logger.error(
                "wxpay notify amount mismatch for order %s: got %s fen, expected %s fen",
                order_no, notify_fen, expected_fen,
            )
            return {"code": "FAIL", "message": "amount mismatch"}

        # ⑦ 标记已支付（幂等）
        if order.status == "pending":
            order.status  = "paid"
            order.paid_at = datetime.now()
            db.add(order)
            await db.commit()

        return {"code": "SUCCESS", "message": "OK"}

    return {"code": "FAIL", "message": "decryption failed"}
