package shop.alien.dining.strategy.payment.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import shop.alien.dining.service.StoreOrderService;
import shop.alien.dining.strategy.payment.PaymentStrategy;
import shop.alien.dining.util.WXPayUtility;
import shop.alien.dining.util.WeChatPayUtil;
import shop.alien.entity.result.R;
import shop.alien.entity.store.LifeDiscountCouponUser;
import shop.alien.entity.store.StoreOrder;
import shop.alien.entity.store.StorePaymentConfig;
import shop.alien.mapper.LifeDiscountCouponUserMapper;
import shop.alien.mapper.StorePaymentConfigMapper;
import shop.alien.util.common.constant.DiscountCouponEnum;
import shop.alien.util.common.constant.PaymentEnum;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.*;
import java.util.concurrent.CompletableFuture;


/**
 * 微信支付小程序策略
 *
 * @author lyx
 * @date 2025/11/20
 */

@Slf4j
@Component
@RequiredArgsConstructor
@RefreshScope
public class WeChatPaymentMininProgramStrategyImpl implements PaymentStrategy {
    /**
     * 微信支付api主机地址
     */
    @Value("${payment.wechatPay.host}")
    private String wechatPayApiHost;
    /**
     * 微信支付预支付路径
     */
    @Value("${payment.wechatPay.business.miniProgram.prePayPath:/v3/pay/transactions/jsapi}")
    private String prePayPath;
    /**
     * 微信支付预支付通知路径
     */
    @Value("${payment.wechatPay.business.miniProgram.prePayNotifyUrl:https://frp-way.com:12473/aliendining/payment/weChatMininNotify}")
    private String prePayNotifyUrl;
    /**
     * 微信支付退款通知路径
     */
    @Value("${payment.wechatPay.business.miniProgram.refundNotifyUrl:https://www.weixin.qq.com/wxpay/pay.php}")
    private String refundNotifyUrl;
    /**
     * 微信支付查询退款状态路径
     */
    @Value("${payment.wechatPay.searchRefundStatusByOutRefundNoPath:/v3/refund/domestic/refunds/{out_refund_no}}")
    private String searchRefundStatusByOutRefundNoPath;

    @Value("${payment.wechatPay.searchOrderByOutTradeNoPath}")
    private String searchOrderByOutTradeNoPath;

    private final StoreOrderService storeOrderService;
    private final StorePaymentConfigMapper storePaymentConfigMapper;
    private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
    private final ObjectMapper objectMapper;

    private static String POSTMETHOD = "POST";
    private static String GETMETHOD = "GET";

    /**
     * 根据店铺ID从 MySQL 获取支付配置（本系统内部调用时使用）
     */
    private StorePaymentConfig getConfigByStoreId(Integer storeId) {
        if (storeId == null) {
            return null;
        }
        LambdaQueryWrapper<StorePaymentConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StorePaymentConfig::getStoreId, storeId);
        return storePaymentConfigMapper.selectOne(wrapper);
    }

    /**
     * 根据微信支付公钥序列号获取支付配置（外部回调如支付通知时使用，通过请求头 Wechatpay-Serial 判断）
     */
    private StorePaymentConfig getConfigByWechatPayPublicKeyId(String wechatPayPublicKeyId) {
        if (!StringUtils.hasText(wechatPayPublicKeyId)) {
            return null;
        }
        LambdaQueryWrapper<StorePaymentConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StorePaymentConfig::getWechatPayPublicKeyId, wechatPayPublicKeyId);
        List<StorePaymentConfig> storePaymentConfigs = storePaymentConfigMapper.selectList(wrapper);
        if (storePaymentConfigs.isEmpty()) {
            return null;
        }
        return storePaymentConfigs.get(0);
    }

    /**
     * 从 StorePaymentConfig 加载商户私钥
     */
    private PrivateKey loadPrivateKeyFromConfig(StorePaymentConfig config) {
        if (config == null) {
            return null;
        }
        if (config.getWechatPrivateKeyFile() != null && config.getWechatPrivateKeyFile().length > 0) {
            String keyStr = new String(config.getWechatPrivateKeyFile(), StandardCharsets.UTF_8);
            return WXPayUtility.loadPrivateKeyFromString(keyStr);
        }
        if (StringUtils.hasText(config.getWechatPrivateKeyPath())) {
            return WXPayUtility.loadPrivateKeyFromPath(config.getWechatPrivateKeyPath());
        }
        return null;
    }

    /**
     * 从 StorePaymentConfig 加载微信支付公钥
     */
    private PublicKey loadPublicKeyFromConfig(StorePaymentConfig config) {
        if (config == null) {
            return null;
        }
        if (config.getWechatPayPublicKeyFile() != null && config.getWechatPayPublicKeyFile().length > 0) {
            String keyStr = new String(config.getWechatPayPublicKeyFile(), StandardCharsets.UTF_8);
            return WXPayUtility.loadPublicKeyFromString(keyStr);
        }
        if (StringUtils.hasText(config.getWechatPayPublicKeyFilePath())) {
            return WXPayUtility.loadPublicKeyFromPath(config.getWechatPayPublicKeyFilePath());
        }
        return null;
    }

    @Override
    public R createPrePayOrder(String price, String subject, String payer, String orderNo, Integer storeId, Integer couponId, Integer payerId,String tablewareFee,String discountAmount,String payAmount) throws Exception {
        // 本系统调用：通过 storeId 从 MySQL 获取店铺支付配置
        if (storeId == null) {
            log.warn("createPrePayOrder 缺少 storeId，无法获取支付配置");
            return R.fail("店铺ID不能为空");
        }
        StorePaymentConfig config = getConfigByStoreId(storeId);
        if (config == null) {
            log.warn("未找到店铺支付配置 storeId={}", storeId);
            return R.fail("未找到该店铺的微信支付配置");
        }
        PrivateKey privateKey = loadPrivateKeyFromConfig(config);
        if (privateKey == null) {
            log.warn("createPrePayOrder, storeId: {}, error: {}", storeId, "店铺微信支付私钥未配置或加载失败");
            return R.fail("店铺微信支付私钥未配置或加载失败");
        }
        String appId = config.getWechatMiniAppId() != null ? config.getWechatMiniAppId() : config.getWechatAppId();
        if (!StringUtils.hasText(appId)) {
            log.warn("createPrePayOrder, storeId: {}, error: {}", storeId, "店铺微信小程序 appId 未配置");
            return R.fail("店铺微信小程序 appId 未配置");
        }
        String mchId = config.getWechatMchId();
        if (!StringUtils.hasText(mchId)) {
            log.warn("createPrePayOrder, 店铺微信支付商户号未配置");
            return R.fail("店铺微信支付商户号未配置");
        }

        DirectAPIv3JsapiPrepayRequest request = new DirectAPIv3JsapiPrepayRequest();
        request.appid = appId;
        request.mchid = mchId;
        request.description = subject;
        request.notifyUrl = prePayNotifyUrl;
        request.amount = new CommonAmountInfo();
        request.amount.total = Long.parseLong(price);
        request.payer = new JsapiReqPayerInfo();
        request.payer.openid = payer;

        String wechatOutTradeNo = orderNo;
        StoreOrder storeOrder = storeOrderService.getOrderByOrderNo(orderNo);
        log.info("createPrePayOrder, orderNo: {}, storeOrder: {}", orderNo, storeOrder);
        if (storeOrder != null ) {
            log.info("createPrePayOrder, orderNo: {}, storeOrder: {}", orderNo, storeOrder);
            if (storeOrder.getPayStatus() != null && storeOrder.getPayStatus() == 1) {
                return R.fail("订单已支付");
            }
            PublicKey publicKey = loadPublicKeyFromConfig(config);
            if (publicKey == null) {
                return R.fail("店铺微信支付公钥未配置或加载失败");
            }
            if(storeOrder.getPayTradeNo() != null) {
                QueryByWxTradeNoRequest q = new QueryByWxTradeNoRequest();
                q.transactionId = storeOrder.getPayTradeNo();
                q.mchid = mchId;
                try {
                    DirectAPIv3QueryResponse wxOrder = searchOrderRun(q, config, privateKey, publicKey);
                    if (wxOrder != null && "SUCCESS".equals(wxOrder.tradeState)) {
                        return R.fail("该支付单已在微信侧支付成功，请勿重复发起支付");
                    }
                } catch (WXPayUtility.ApiException e) {
                    if (e.getStatusCode() != 404 && !"ORDER_NOT_EXIST".equals(e.getErrorCode())) {
                        log.error("预支付前查询微信订单失败 out_trade_no={}, status={}, code={}, msg={}",
                                storeOrder.getPayTradeNo(), e.getStatusCode(), e.getErrorCode(), e.getErrorMessage());
                        return R.fail("查询微信支付订单失败：" + (e.getErrorMessage() != null ? e.getErrorMessage() : e.getMessage()));
                    }
                }
            }
            String newPayTradeNo = "WX" + storeOrder.getId() + "_" + System.currentTimeMillis();
            storeOrder.setPayTradeNo(newPayTradeNo);
            wechatOutTradeNo = newPayTradeNo;
            log.info("未支付场景已换新微信商户单号 orderNo={}, payTradeNo={}", orderNo, newPayTradeNo);
            storeOrder.setCouponId(couponId);
            storeOrder.setPayUserId(payerId);
            storeOrder.setTablewareFee(new BigDecimal(tablewareFee));
            storeOrder.setDiscountAmount(new BigDecimal(discountAmount));
            storeOrder.setPayAmount(new BigDecimal(payAmount));
            if (!storeOrderService.updateById(storeOrder)) {
                log.error("更新订单失败 orderNo={}", orderNo);
                return R.fail("更新订单失败");
            }
        }
        request.outTradeNo = wechatOutTradeNo;

        try {
            DirectAPIv3JsapiPrepayResponse response = doCreatePrePayOrder(request, config, privateKey);
            log.info("微信预支付订单创建成功，预支付ID：{}", response.prepayId);
            Map<String, String> result = new HashMap<>();
            response.prepayId = "prepay_id=" + response.prepayId;
            result.put("prepayId", response.prepayId);
            result.put("appId", appId);
            result.put("mchId", mchId);
            result.put("orderNo", request.outTradeNo);
            long timestamp = System.currentTimeMillis() / 1000;
            String nonce = WXPayUtility.createNonce(32);
            String message = String.format("%s\n%s\n%s\n%s\n", appId, timestamp, nonce, response.prepayId);
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            result.put("signType", "RSA");
            result.put("sign", Base64.getEncoder().encodeToString(sign.sign()));
            result.put("timestamp", String.valueOf(timestamp));
            result.put("nonce", nonce);
            result.put("transactionId", request.outTradeNo);
            return R.data(result);
        } catch (WXPayUtility.ApiException e) {
            log.error("微信支付预支付失败，状态码：{}，错误信息：{}", e.getErrorCode(), e.getMessage());
            return R.fail(e.getMessage());
        }
    }

    @Override
    public R handleNotify(String notifyData, HttpServletRequest request) throws Exception {
        log.info("[微信支付回调] 进入 handleNotify, notifyData 长度={}", notifyData != null ? notifyData.length() : 0);
        // 外部回调：通过请求头 Wechatpay-Serial 从 MySQL 查找对应店铺的支付配置
        String serial = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");

        if (serial == null || signature == null || timestamp == null || nonce == null) {
            log.warn("微信支付回调验签失败：核心头参数缺失 serial={}, signature={}, timestamp={}, nonce={}",
                    serial, signature, timestamp, nonce);
            return R.fail("验签参数缺失");
        }

        if (signature.startsWith("WECHATPAY/SIGNTEST/")) {
            log.info("[微信支付回调] 收到签名探测请求，直接应答成功");
            return R.success("OK");
        }

        StorePaymentConfig config = getConfigByWechatPayPublicKeyId(serial);
        if (config == null) {
            log.warn("微信支付回调：未找到公钥序列号对应的店铺配置 serial={}", serial);
            return R.fail("未找到对应支付配置");
        }
        PublicKey wechatPayPublicKey = loadPublicKeyFromConfig(config);
        if (wechatPayPublicKey == null) {
            log.warn("微信支付回调：加载公钥失败 serial={}", serial);
            return R.fail("验签公钥加载失败");
        }

        StringBuilder signStr = new StringBuilder();
        signStr.append(timestamp).append("\n");
        signStr.append(nonce).append("\n");
        signStr.append(notifyData).append("\n");
        Signature sign = Signature.getInstance("SHA256withRSA");
        byte[] signatureBytes = Base64.getDecoder().decode(signature);
        sign.initVerify(wechatPayPublicKey);
        sign.update(signStr.toString().getBytes(StandardCharsets.UTF_8));

        if (sign.verify(signatureBytes)) {
            final String notifyDataCopy = notifyData;
            final StorePaymentConfig configCopy = config;
            CompletableFuture.runAsync(() -> processNotifyBusiness(notifyDataCopy, configCopy));
            return R.success("OK");
        } else {
            return R.fail("Verified error");
        }
    }

    /**
     * 异步处理回调业务：解密并更新订单状态（文档建议应答后再处理业务，避免超时）
     * @param config 已通过 Wechatpay-Serial 解析得到的店铺支付配置，内含 apiV3Key
     */
    private void processNotifyBusiness(String notifyData, StorePaymentConfig config) {
        try {
            if (config == null || !StringUtils.hasText(config.getApiV3Key())) {
                log.warn("微信支付回调：无可用 apiV3Key 无法解密");
                return;
            }
            JsonNode rootNode = objectMapper.readTree(notifyData);
            JsonNode resourceNode = rootNode.get("resource");
            if (resourceNode == null) {
                log.warn("微信支付回调报文无resource字段");
                return;
            }
            String encryptAlgorithm = resourceNode.get("algorithm").asText();
            String resourceNonce = resourceNode.get("nonce").asText();
            String associatedData = resourceNode.has("associated_data") && !resourceNode.get("associated_data").isNull()
                    ? resourceNode.get("associated_data").asText() : "";
            String ciphertext = resourceNode.get("ciphertext").asText();
            if (!"AEAD_AES_256_GCM".equals(encryptAlgorithm)) {
                log.warn("不支持的加密算法：{}", encryptAlgorithm);
                return;
            }
            String plainBusinessData = WeChatPayUtil.decrypt(
                    config.getApiV3Key(), resourceNonce, associatedData, ciphertext);
            log.info("微信支付回调解密后的业务信息：{}", plainBusinessData);
            JSONObject jsonObject = JSONObject.parseObject(plainBusinessData);
            String tradeState = jsonObject.getString("trade_state");
            if ("SUCCESS".equals(tradeState)) {
                String outTradeNo = jsonObject.getString("out_trade_no");
                StoreOrder storeOrder = storeOrderService.getOne(new QueryWrapper<StoreOrder>().eq("order_no", outTradeNo));
                if (storeOrder == null && StringUtils.hasText(outTradeNo)) {
                    storeOrder = storeOrderService.getOne(new QueryWrapper<StoreOrder>().eq("pay_trade_no", outTradeNo));
                }
                if (storeOrder != null && storeOrder.getPayStatus() != 1) {
                    storeOrder.setPayStatus(1);
                    storeOrder.setOrderStatus(1);
                    storeOrder.setPayType(1);
                    storeOrder.setPayTime(new Date());
                    if (storeOrderService.updateById(storeOrder)) {
                        log.info("小程序更新订单成功，订单号outTradeNo：{}", outTradeNo);
                        if (storeOrder.getCouponId() != null && storeOrder.getPayUserId() != null) {
                            LambdaQueryWrapper<LifeDiscountCouponUser> couponUserWrapper = new LambdaQueryWrapper<>();
                            couponUserWrapper.eq(LifeDiscountCouponUser::getUserId, storeOrder.getPayUserId());
                            couponUserWrapper.eq(LifeDiscountCouponUser::getCouponId, storeOrder.getCouponId());
                            couponUserWrapper.eq(LifeDiscountCouponUser::getStatus,
                                    Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
                            couponUserWrapper.eq(LifeDiscountCouponUser::getDeleteFlag, 0);
                            couponUserWrapper.orderByDesc(LifeDiscountCouponUser::getCreatedTime);
                            couponUserWrapper.last("LIMIT 1");
                            LifeDiscountCouponUser couponUser = lifeDiscountCouponUserMapper.selectOne(couponUserWrapper);
                            if (couponUser != null) {
                                couponUser.setStatus(Integer.parseInt(DiscountCouponEnum.HAVE_BEEN_USED.getValue()));
                                couponUser.setUseTime(new Date());
                                lifeDiscountCouponUserMapper.updateById(couponUser);
                                log.info("支付成功，已更新 life_discount_coupon_user 为已使用, id={}", couponUser.getId());
                            }
                        }
                        // 支付完成后，清空购物车和重置餐桌状态（保留订单数据，不删除订单）
                        try {
                            storeOrderService.resetTableAfterPayment(storeOrder.getTableId());
                            log.info("支付完成后重置餐桌成功, tableId={}", storeOrder.getTableId());
                        } catch (Exception e) {
                            log.error("支付完成后重置餐桌失败, tableId={}, error={}", storeOrder.getTableId(), e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("[微信支付回调] 异步处理业务异常", e);
        }
    }

    @Override
    public R searchOrderByOutTradeNoPath(String transactionId, Integer storeId) throws Exception {
        log.info("查询微信支付订单状态，交易订单号：{}, storeId：{}", transactionId, storeId);
        if (storeId == null) {
            return R.fail("店铺ID不能为空");
        }
        StorePaymentConfig config = getConfigByStoreId(storeId);
        if (config == null) {
            return R.fail("未找到该店铺的微信支付配置");
        }
        PrivateKey privateKey = loadPrivateKeyFromConfig(config);
        PublicKey publicKey = loadPublicKeyFromConfig(config);
        if (privateKey == null || publicKey == null) {
            return R.fail("店铺微信支付密钥未配置或加载失败");
        }
        QueryByWxTradeNoRequest request = new QueryByWxTradeNoRequest();
        request.transactionId = transactionId;
        request.mchid = config.getWechatMchId();
        try {
            DirectAPIv3QueryResponse response = searchOrderRun(request, config, privateKey, publicKey);
            return R.data(response);
        } catch (WXPayUtility.ApiException e) {
            log.error("查询微信支付订单状态失败，状态码：{}，错误信息：{}", e.getErrorCode(), e.getMessage());
            return R.fail(e.getMessage());
        }
    }

    public DirectAPIv3QueryResponse searchOrderRun(QueryByWxTradeNoRequest request,
                                                   StorePaymentConfig config,
                                                   PrivateKey privateKey,
                                                   PublicKey wechatPayPublicKey) {
        String uri = searchOrderByOutTradeNoPath;
        uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
        Map<String, Object> args = new HashMap<>();
        args.put("mchid", request.mchid);
        String queryString = WXPayUtility.urlEncode(args);
        if (!queryString.isEmpty()) {
            uri = uri + "?" + queryString;
        }

        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(request.mchid, config.getMerchantSerialNumber(), privateKey, GETMETHOD, uri, null));
        reqBuilder.method(GETMETHOD, null);
        Request httpRequest = reqBuilder.build();

        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, DirectAPIv3QueryResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }

    @Override
    public String handleRefund(Map<String, String> params) throws Exception {
        return null;
    }

    @Override
    public R searchRefundRecordByOutRefundNo(String outRefundNo, Integer storeId) throws Exception {
        Logger logger = LoggerFactory.getLogger(this.getClass());

        if (outRefundNo == null || outRefundNo.trim().isEmpty()) {
            logger.error("微信退款查询失败：外部退款单号为空");
            return R.fail("外部退款单号不能为空");
        }
        if (storeId == null) {
            return R.fail("店铺ID不能为空");
        }
        StorePaymentConfig config = getConfigByStoreId(storeId);
        if (config == null) {
            return R.fail("未找到该店铺的微信支付配置");
        }
        PrivateKey privateKey = loadPrivateKeyFromConfig(config);
        PublicKey publicKey = loadPublicKeyFromConfig(config);
        if (privateKey == null || publicKey == null) {
            return R.fail("店铺微信支付密钥未配置或加载失败");
        }

        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(outRefundNo);

        try {
            Refund response = doSearchRefundRecordByOutRefundNo(request, config, privateKey, publicKey);

            // 3. 空值校验（避免response为空导致空指针）
            if (response == null) {
                logger.error("微信退款查询失败：外部退款单号{}，返回结果为空", outRefundNo);
                return R.fail("微信支付查询退款记录失败：返回结果为空");
            }

            logger.info("微信退款查询结果：外部退款单号{}，退款状态{}",
                    outRefundNo, response.getStatus());

            // 4. 细化退款状态判断（覆盖微信支付核心退款状态）
            String refundStatus = String.valueOf(response.getStatus());
            switch (refundStatus) {
                case "SUCCESS":
                    // 退款成功：执行成功业务逻辑（如更新订单状态、通知用户等）
                    logger.info("退款成功：外部退款单号{}", outRefundNo);
                    // TODO: 补充你的成功业务逻辑（例：updateOrderRefundStatus(outRefundNo, "SUCCESS");）
                    return R.data(response);

                case "REFUNDCLOSE":
                    // 退款关闭：执行关闭逻辑（如记录关闭原因、人工介入等）
                    logger.warn("退款关闭：外部退款单号{}，原因{}", outRefundNo);
                    return R.fail("微信支付退款已关闭");

                case "PROCESSING":
                    // 退款处理中：执行等待逻辑（如提示用户等待、定时任务重试等）
                    logger.info("退款处理中：外部退款单号{}", outRefundNo);
                    return R.fail("微信支付退款处理中，请稍后再查");

                case "CHANGE":
                    // 退款异常：执行异常处理（如记录异常、人工核查等）
                    logger.error("退款异常：外部退款单号{}", outRefundNo);
                    return R.fail("微信支付退款异常");

                default:
                    // 未知状态：兜底处理
                    logger.error("退款状态未知：外部退款单号{}，状态码{}", outRefundNo, refundStatus);
                    return R.fail("微信支付查询退款记录失败：未知状态码" + refundStatus);
            }

        } catch (WXPayUtility.ApiException e) {
            // 5. 异常处理：细化异常日志，便于排查
            logger.error("微信退款查询API异常：外部退款单号{}，错误码{}，错误信息{}",
                    outRefundNo, e.getErrorCode(), e.getMessage(), e);
            return R.fail("微信支付查询退款记录失败：" + e.getMessage() + "（错误码：" + e.getErrorCode() + "）");
        } catch (Exception e) {
            // 6. 兜底异常：捕获非API异常（如空指针、网络异常等）
            logger.error("微信退款查询系统异常：外部退款单号{}", outRefundNo, e);
            return R.fail("微信支付查询退款记录失败：系统异常，请联系管理员");
        }
    }

    @Override
    public String getType() {
        return PaymentEnum.WECHAT_PAY_MININ_PROGRAM.getType();
    }

    public Refund doSearchRefundRecordByOutRefundNo(QueryByOutRefundNoRequest request,
                                                   StorePaymentConfig config,
                                                   PrivateKey privateKey,
                                                   PublicKey wechatPayPublicKey) {
        String uri = searchRefundStatusByOutRefundNoPath;
        uri = uri.replace("{out_refund_no}", WXPayUtility.urlEncode(request.getOutRefundNo()));
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, GETMETHOD, uri, null));
        reqBuilder.method(GETMETHOD, null);
        Request httpRequest = reqBuilder.build();

        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, Refund.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }

    public DirectAPIv3JsapiPrepayResponse doCreatePrePayOrder(DirectAPIv3JsapiPrepayRequest request,
                                                             StorePaymentConfig config,
                                                             PrivateKey privateKey) {
        String uri = prePayPath;
        String reqBody = WXPayUtility.toJson(request);

        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, POSTMETHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(POSTMETHOD, requestBody);
        Request httpRequest = reqBuilder.build();

        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                PublicKey wechatPayPublicKey = loadPublicKeyFromConfig(config);
                WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, DirectAPIv3JsapiPrepayResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }

    public static class DirectAPIv3JsapiPrepayRequest {
        @SerializedName("appid")
        public String appid;

        @SerializedName("mchid")
        public String mchid;

        @SerializedName("description")
        public String description;

        @SerializedName("out_trade_no")
        public String outTradeNo;

        @SerializedName("time_expire")
        public String timeExpire;

        @SerializedName("attach")
        public String attach;

        @SerializedName("notify_url")
        public String notifyUrl;

        @SerializedName("goods_tag")
        public String goodsTag;

        @SerializedName("support_fapiao")
        public Boolean supportFapiao;

        @SerializedName("amount")
        public CommonAmountInfo amount;

        @SerializedName("payer")
        public JsapiReqPayerInfo payer;

        @SerializedName("detail")
        public CouponInfo detail;

        @SerializedName("scene_info")
        public CommonSceneInfo sceneInfo;

        @SerializedName("settle_info")
        public SettleInfo settleInfo;
    }

    public static class DirectAPIv3JsapiPrepayResponse {
        @SerializedName("prepay_id")
        public String prepayId;
    }

    public static class CommonAmountInfo {
        @SerializedName("total")
        public Long total;

        @SerializedName("currency")
        public String currency;
    }

    public static class JsapiReqPayerInfo {
        @SerializedName("openid")
        public String openid;
    }

    public static class CouponInfo {
        @SerializedName("cost_price")
        public Long costPrice;

        @SerializedName("invoice_id")
        public String invoiceId;

        @SerializedName("goods_detail")
        public List<GoodsDetail> goodsDetail;
    }

    public static class CommonSceneInfo {
        @SerializedName("payer_client_ip")
        public String payerClientIp;

        @SerializedName("device_id")
        public String deviceId;

        @SerializedName("store_info")
        public StoreInfo storeInfo;
    }

    public static class SettleInfo {
        @SerializedName("profit_sharing")
        public Boolean profitSharing;
    }

    public static class GoodsDetail {
        @SerializedName("merchant_goods_id")
        public String merchantGoodsId;

        @SerializedName("wechatpay_goods_id")
        public String wechatpayGoodsId;

        @SerializedName("goods_name")
        public String goodsName;

        @SerializedName("quantity")
        public Long quantity;

        @SerializedName("unit_price")
        public Long unitPrice;
    }

    public static class StoreInfo {
        @SerializedName("id")
        public String id;

        @SerializedName("name")
        public String name;

        @SerializedName("area_code")
        public String areaCode;

        @SerializedName("address")
        public String address;
    }

    public static class QueryByWxTradeNoRequest {
        @SerializedName("mchid")
        @Expose(serialize = false)
        public String mchid;

        @SerializedName("transaction_id")
        @Expose(serialize = false)
        public String transactionId;
    }

    public static class DirectAPIv3QueryResponse {
        @SerializedName("appid")
        public String appid;

        @SerializedName("mchid")
        public String mchid;

        @SerializedName("out_trade_no")
        public String outTradeNo;

        @SerializedName("transaction_id")
        public String transactionId;

        @SerializedName("trade_type")
        public String tradeType;

        @SerializedName("trade_state")
        public String tradeState;

        @SerializedName("trade_state_desc")
        public String tradeStateDesc;

        @SerializedName("bank_type")
        public String bankType;

        @SerializedName("attach")
        public String attach;

        @SerializedName("success_time")
        public String successTime;

        @SerializedName("payer")
        public CommRespPayerInfo payer;

        @SerializedName("amount")
        public CommRespAmountInfo amount;

        @SerializedName("scene_info")
        public CommRespSceneInfo sceneInfo;

        @SerializedName("promotion_detail")
        public List<PromotionDetail> promotionDetail;
    }
    public static class CommRespPayerInfo {
        @SerializedName("openid")
        public String openid;
    }

    public static class CommRespAmountInfo {
        @SerializedName("total")
        public Long total;

        @SerializedName("payer_total")
        public Long payerTotal;

        @SerializedName("currency")
        public String currency;

        @SerializedName("payer_currency")
        public String payerCurrency;
    }

    public static class CommRespSceneInfo {
        @SerializedName("device_id")
        public String deviceId;
    }

    public static class PromotionDetail {
        @SerializedName("coupon_id")
        public String couponId;

        @SerializedName("name")
        public String name;

        @SerializedName("scope")
        public String scope;

        @SerializedName("type")
        public String type;

        @SerializedName("amount")
        public Long amount;

        @SerializedName("stock_id")
        public String stockId;

        @SerializedName("wechatpay_contribute")
        public Long wechatpayContribute;

        @SerializedName("merchant_contribute")
        public Long merchantContribute;

        @SerializedName("other_contribute")
        public Long otherContribute;

        @SerializedName("currency")
        public String currency;

        @SerializedName("goods_detail")
        public List<GoodsDetailInPromotion> goodsDetail;
    }

    public static class GoodsDetailInPromotion {
        @SerializedName("goods_id")
        public String goodsId;

        @SerializedName("quantity")
        public Long quantity;

        @SerializedName("unit_price")
        public Long unitPrice;

        @SerializedName("discount_amount")
        public Long discountAmount;

        @SerializedName("goods_remark")
        public String goodsRemark;
    }
}

