package com.kym.service.wechat.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.kym.common.config.WxPayConfig;
import com.kym.common.constant.ResponseEnum;
import com.kym.common.exception.BusinessException;
import com.kym.common.utils.CommUtil;
import com.kym.common.utils.LambadaTools;
import com.kym.common.utils.OrderUtils;
import com.kym.entity.Account;
import com.kym.entity.*;
import com.kym.service.*;
import com.kym.service.cache.KymCache;
import com.kym.service.wechat.WxPayService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @author skyline
 * @description 微信支付
 * @date 2023-08-10 14:03
 */
@Service
public class WxPayServiceImpl implements WxPayService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WxPayServiceImpl.class);

    public static JsapiService jsapiService;

    public static RefundService refundService;

    public static Config config;
    private final WxPayConfig conf;

    private final WalletDetailService walletDetailService;

    private final PayLogService payLogService;

    private final AccountService accountService;

    private final RefundLogService refundLogService;

    private final RechargeConfigService rechargeConfigService;
    private final SplitRecordService splitRecordService;
    private final WashOrderService washOrderService;
    private final MpMsgTemplateService mpMsgTemplateService;
    private final RechargePromotionService rechargePromotionService;
    private final UserService userService;


    /**
     * 微信支付专用，支持自动签名验签解密等
     */
    private OkHttpClientAdapter wxHttpClient;


    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService,
                            PayLogService payLogService, AccountService accountService,
                            RefundLogService refundLogService,
                            ActivityService activityService,
                            RechargeConfigService rechargeConfigService, SplitRecordService splitRecordService,
                            WashOrderService washOrderService, MpMsgTemplateService mpMsgTemplateService,
                            RechargePromotionService rechargePromotionService,
                            UserService userService) {
        this.conf = conf;
        this.walletDetailService = walletDetailService;
        this.payLogService = payLogService;
        this.accountService = accountService;
        this.refundLogService = refundLogService;
        this.rechargeConfigService = rechargeConfigService;
        this.splitRecordService = splitRecordService;
        this.washOrderService = washOrderService;
        this.mpMsgTemplateService = mpMsgTemplateService;
        this.rechargePromotionService = rechargePromotionService;
        this.userService = userService;
    }

    /**
     * 商户订单号查询订单
     */
    public static Transaction queryOrderByOutTradeNo() {

        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        // 调用接口
        return jsapiService.queryOrderByOutTradeNo(request);
    }

    /**
     * 初始化
     *
     * @throws IOException
     */
    @PostConstruct
    void init() throws IOException {
        String privateKey;
        File file = new File(conf.getKeyPath());
        if (file.exists()) {
            privateKey = IoUtil.read(new FileInputStream(file), StandardCharsets.UTF_8);
        } else {
            ClassPathResource resource = new ClassPathResource(conf.getKeyPath());
            privateKey = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
        }
        config = new RSAAutoCertificateConfig.Builder()
                .merchantId(conf.getMchid()) // 商户号
                .privateKey(privateKey)//商户API私钥路径
                .merchantSerialNumber(conf.getMchsn()) // 商户证书序列号
                .apiV3Key(conf.getV3key()) // 商户APIV3密钥
                .build();
        jsapiService = new JsapiService.Builder().config(config).build();
        refundService = new RefundService.Builder().config(config).build();

        wxHttpClient = (OkHttpClientAdapter) new DefaultHttpClientBuilder().newInstance().config(config).build();

//        // 初始化微信电子发票开发配置，主要是回调地址 todo 区块链电子发票开通后放开
//         devConfig();
    }

    /**
     * 回调签名验证失败排查指引 （body中字段顺序变化造成验签失败是个大坑）
     * https://developers.weixin.qq.com/community/pay/article/doc/0004a879f60928d89340b8b9f64c13
     * https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/issues/152
     *
     * @param request
     * @return
     */
    @SneakyThrows
    Object[] handleWxNotify(HttpServletRequest request) {
        var no = RandomUtil.randomInt(1000, 9999);
        var signature = request.getHeader("Wechatpay-Signature");
        var serial = request.getHeader("Wechatpay-Serial");
        var nonce = request.getHeader("Wechatpay-Nonce");
        var timestamp = request.getHeader("Wechatpay-Timestamp");
        var signatureType = request.getHeader("Wechatpay-Signature-Type");

        // 应对探测流量
        if (signature.contains("SIGNTEST")) {
            throw new BusinessException("接收到签名探测流量");
        }

        LOGGER.info("微信支付回调{}： Request参数： signature:{},serial:{},nonce:{},timestamp:{},signatureType:{}", no, signature, serial, nonce, timestamp, signatureType);

        ServletInputStream inputStream = request.getInputStream();
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        for (int lenght; (lenght = inputStream.read(buffer)) != -1; ) {
            result.write(buffer, 0, lenght);
        }
        var body = result.toString();

        LOGGER.info("微信支付回调{}：\nBody数据:\n{}", no, result);

        // 构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce) // 随机数
                .signature(signature)
                .timestamp(timestamp)
                .body(body)
                .build();
        LOGGER.info("微信支付回调{}：构造 RequestParam完毕", no);

        // 如果已经初始化了 RSAAutoCertificateConfig，可直接使用
        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser((NotificationConfig) config);
        return new Object[]{requestParam, parser, no};
    }

    /**
     * JSAPI支付下单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PrepayWithRequestPaymentResponse wxPay(Long rechargeConfigId) {
        // 充值配置
        var rechargeConfig = rechargeConfigService.getById(rechargeConfigId);

        if ((rechargeConfig == null) || rechargeConfig.getRechargeAmount() <= 0) {
            throw new BusinessException(ResponseEnum.WX_PAY_AMOUNT_ERROR);
        }

        var rechargeAmount = rechargeConfig.getRechargeAmount();
        var openid = StpUtil.getSession().getString("openid");
        var userId = StpUtil.getLoginIdAsLong();
        // 充值归属站点以服务端用户归属为准，不信任客户端传值（防止分账归属被篡改）
        var stationId = getUserStationId(userId);
        // 生成订单号
        String outTradeNo = OrderUtils.getOrderNo();
        // 创建钱包流水
        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_充值)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(userId)
                .setAmount(rechargeAmount)
                .setOrderNo(outTradeNo);
        walletDetailService.save(walletDetail);

        // request.setXxx(val)设置所需参数，具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        // 传入金额单位为分
        amount.setTotal(rechargeAmount);
        request.setAmount(amount);
        request.setAppid(conf.getAppid());
        request.setMchid(conf.getMchid());
        request.setDescription("超级进化车生活充值");
        request.setNotifyUrl(conf.getNotifyUrl());
        request.setOutTradeNo(outTradeNo);
        // 把stationId传给微信，微信回调时携带再取出，记录是充值到哪个站点
        request.setAttach(stationId);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        // response包含了调起支付所需的所有参数，可直接用于前端调起支付
        return service.prepayWithRequestPayment(request);
    }

    /**
     * 专属优惠活动充值支付下单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PrepayWithRequestPaymentResponse promotionPay(String promotionToken) {
        var promotion = rechargePromotionService.getByToken(promotionToken);
        if (promotion == null) {
            throw new BusinessException("优惠活动不存在或已过期");
        }

        var userId = StpUtil.getLoginIdAsLong();
        if (!rechargePromotionService.checkUserEligible(promotion, userId)) {
            throw new BusinessException("您不在本次活动范围内");
        }

        var openid = StpUtil.getSession().getString("openid");
        // 充值归属站点以服务端用户归属为准，不信任客户端传值（防止分账归属被篡改）
        var stationId = getUserStationId(userId);
        var outTradeNo = OrderUtils.getOrderNo();

        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_充值)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(userId)
                .setAmount(promotion.getRechargeAmount())
                .setOrderNo(outTradeNo)
                .setPromotionId(promotion.getId())
                .setSource("WX_PAY");
        walletDetailService.save(walletDetail);

        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(promotion.getRechargeAmount());
        request.setAmount(amount);
        request.setAppid(conf.getAppid());
        request.setMchid(conf.getMchid());
        request.setDescription("超级进化车生活充值(专属优惠)");
        request.setNotifyUrl(conf.getNotifyUrl());
        request.setOutTradeNo(outTradeNo);
        request.setAttach(stationId);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        return service.prepayWithRequestPayment(request);
    }

    /**
     * 微信支付订单号查询订单
     */
    public Transaction queryOrderById(String transactionId) {
        QueryOrderByIdRequest request = new QueryOrderByIdRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setMchid(conf.getMchid());
        request.setTransactionId(transactionId);
        // 调用接口
        return jsapiService.queryOrderById(request);
    }

    /**
     * 服务端获取用户归属站点（KymCache 优先，未命中查库兜底），用于充值分账归属
     */
    private String getUserStationId(Long userId) {
        var stationId = KymCache.INSTANCE.getUserStationId(userId);
        if (stationId == null) {
            var user = userService.getById(userId);
            stationId = user != null ? user.getStationId() : null;
        }
        return stationId;
    }

    /**
     * 关闭订单
     *
     * @param outTradeNo
     */
    public void closeOrder(String outTradeNo) {
        CloseOrderRequest request = new CloseOrderRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setMchid(conf.getMchid());
        request.setOutTradeNo(outTradeNo);
        // 调用接口
        jsapiService.closeOrder(request);
    }

    /**
     * 微信支付结果通知（充值完成）
     *
     * @param request
     * @return
     */
    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<Object> wxNotify(HttpServletRequest request) {
        try {
            var notifyRes = handleWxNotify(request);
            // 以支付通知回调为例，验签、解密并转换成 Transaction
            Transaction transaction = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], Transaction.class);
            LOGGER.info("微信支付回调{}：验签解密完毕，数据：{}", notifyRes[2], transaction);
            // 判断是否已经接收处理过通知
            if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
                return ResponseEntity.status(HttpStatus.OK).build();
            }

            DateTime dt = DateUtil.parse(transaction.getSuccessTime());
            LocalDateTime successTime = LocalDateTimeUtil.of(dt);

            // 钱包流水
            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo(), WalletDetail.TYPE_充值);
            if (walletDetail != null) {

                var stationId = transaction.getAttach();

                // 确定赠款金额：优先使用优惠活动配置，否则使用站点充值配置
                Integer grantsAmount;
                if (walletDetail.getPromotionId() != null) {
                    var promotion = rechargePromotionService.getById(walletDetail.getPromotionId());
                    grantsAmount = promotion != null ? promotion.getGrantsAmount() : 0;
                } else {
                    var rechargeConfig = rechargeConfigService.getByStationAndAmount(stationId, walletDetail.getAmount());
                    if (rechargeConfig == null) {
                        LOGGER.error("微信支付回调：未找到充值配置，stationId={}, amount={}", stationId, walletDetail.getAmount());
                        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
                    }
                    grantsAmount = rechargeConfig.getGrantsAmount();
                }

                // 更新余额（赠款计入不可退优惠金额）
                var account = accountService.getAccountByUserId(walletDetail.getUserId());
                accountService.lambdaUpdate().setSql("balance = balance + {0}, recharge_balance = recharge_balance + {0}, grants_balance = grants_balance + {1}, discount_amount = discount_amount + {1}", transaction.getAmount().getTotal(), grantsAmount)
                        .eq(Account::getUserId, walletDetail.getUserId()).update();

                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
                walletDetail.setSource("WX_PAY");
                walletDetail.setCurrency(transaction.getAmount().getCurrency());
                walletDetail.setAmount(transaction.getAmount().getTotal());
                walletDetail.setGrantsAmount(grantsAmount);
                walletDetail.setBeforeBalance(account.getBalance());
                walletDetail.setAfterBalance(account.getBalance() + walletDetail.getAmount());
                walletDetail.setBeforeGrantsBalance(account.getGrantsBalance());
                walletDetail.setAfterGrantsBalance(account.getGrantsBalance() + grantsAmount);
                walletDetail.setTransactionTime(successTime);
                walletDetailService.updateById(walletDetail);

                // 支付记录（幂等门闩：先落库 payLog，唯一索引 uk_out_trade_no 兜底并发重复回调）
                var payLog = new PayLog();
                payLog.setUserId(walletDetail.getUserId());
                payLog.setOpenid(transaction.getPayer().getOpenid());
                payLog.setBankType(transaction.getBankType());
                payLog.setMchId(transaction.getMchid());
                payLog.setOutTradeNo(transaction.getOutTradeNo());
                payLog.setTransactionId(transaction.getTransactionId());
                payLog.setSuccessTime(successTime);
                payLog.setTradeType(transaction.getTradeType().name());
                payLog.setTradeState(transaction.getTradeState().name());
                payLog.setAttach(transaction.getAttach());
                var totalAmount = transaction.getAmount().getTotal();
                payLog.setTotal(totalAmount);
                payLog.setCurrency(transaction.getAmount().getCurrency());
                payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
                payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
                try {
                    payLogService.save(payLog);
                } catch (DuplicateKeyException e) {
                    // 并发重复回调：唯一索引拦截，视为已处理
                    LOGGER.warn("微信支付回调重复投递已被唯一索引拦截，outTradeNo={}", transaction.getOutTradeNo());
                    return ResponseEntity.status(HttpStatus.OK).build();
                }

                // V2 结算方案：充值资金记录分账流水，结算日统一处理，不再即时转入站点账户
                // 无归属站点的用户充值暂不生成分账记录，首次消费时追溯补建
                if (CommUtil.isNotEmptyAndNull(stationId) && !"000".equals(stationId)) {
                    var splitRecord = new SplitRecord()
                            .setFromStationId(stationId)
                            .setToStationId(stationId)
                            .setTradeNo(transaction.getTransactionId())
                            .setAmount(totalAmount)
                            .setType(SplitRecord.TYPE_RECHARGE);
                    splitRecordService.save(splitRecord);
                }

                // 发送公众号消息
                mpMsgTemplateService.sendPaymentSuccessMsg(payLog, walletDetail.getAfterBalance());

                LOGGER.info("微信支付回调{}：业务处理结束", notifyRes[2]);
                return ResponseEntity.status(HttpStatus.OK).build();

            } else {
                LOGGER.error("微信支付通知处理异常，资金流水为空，回调信息：{}", transaction);
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "资金流水为空"));
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信支付通知验签失败", e);
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
        } catch (Exception e) {
            // 业务处理失败返回 500，微信会重试投递；重试时由唯一索引/幂等快路径保证不会重复入账
            LOGGER.error("微信支付通知处理异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "处理失败"));
        }
    }

    /**
     * 线下充值（运营人员代充）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void offlineRecharge(Long userId, Integer amount, Integer grantsAmount, String remark) {
        var account = accountService.getAccountByUserId(userId);
        if (account == null) {
            throw new BusinessException("用户账户不存在");
        }

        var tradeNo = OrderUtils.getOrderNo();
        int gAmount = grantsAmount != null ? grantsAmount : 0;

        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_充值)
                .setStatus(WalletDetail.STATUS_已确认)
                .setUserId(userId)
                .setAmount(amount)
                .setGrantsAmount(gAmount)
                .setOrderNo(tradeNo)
                .setSource("MANUAL")
                .setRemark(remark)
                .setBeforeBalance(account.getBalance())
                .setAfterBalance(account.getBalance() + amount)
                .setBeforeGrantsBalance(account.getGrantsBalance())
                .setAfterGrantsBalance(account.getGrantsBalance() + gAmount)
                .setTransactionTime(LocalDateTime.now());
        walletDetailService.save(walletDetail);

        accountService.lambdaUpdate()
                .setSql("balance = balance + {0}, recharge_balance = recharge_balance + {0}, grants_balance = grants_balance + {1}, discount_amount = discount_amount + {1}",
                        amount, gAmount)
                .eq(Account::getUserId, userId)
                .update();

        var stationId = KymCache.INSTANCE.getUserStationId(userId);
        var splitRecord = new SplitRecord()
                .setFromStationId(stationId)
                .setToStationId(stationId)
                .setTradeNo(tradeNo)
                .setAmount(amount)
                .setType(SplitRecord.TYPE_RECHARGE);
        splitRecordService.save(splitRecord);

        LOGGER.info("线下充值完成：userId={}, amount={}, grantsAmount={}, tradeNo={}", userId, amount, gAmount, tradeNo);
    }

    /**
     * 申请退款
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyWxRefund(Long userId, String reason) {
//        var userId = StpUtil.getLoginIdAsLong();
        var washOrder = washOrderService.getOrderInProgressByUserId(userId);
        // 洗车未完结订单校验
        if (washOrder != null) {
            throw new BusinessException("存在未完结的订单，请完成所有订单完结之后重试");
        }

        // 行锁读取账户，防止并发退款请求同时通过余额校验造成重复退款
        var account = accountService.lambdaQuery().eq(Account::getUserId, userId).last("FOR UPDATE").one();
        if (account == null) {
            throw new BusinessException("用户账户不存在");
        }
        // 退款金额以充值余额为准（纯赠款余额不可退），校验与冻结/退款口径一致
        if (account.getRechargeBalance() <= 0) {
            throw new BusinessException("充值余额不足，无需退款");
        }
        // // 校验余额大于优惠金额
        // if (account.getBalance() <= account.getDiscountAmount()) {
        //     throw new BusinessException("不可退金额高于钱包余额，不支持退款");
        // }

        // 将充值余额转移至冻结余额，同时清零充值余额和赠款余额
        accountService.lambdaUpdate().setSql(" frozen_amount = (frozen_amount + recharge_balance), balance = 0, recharge_balance = 0, grants_balance = 0").eq(Account::getUserId, userId).update();


        // 退款金额=充值余额（由于现在的资金消耗逻辑是先消耗充值余额再消耗赠款，所以不考虑优惠金额的影响）
        AtomicInteger refundAmount = new AtomicInteger(account.getRechargeBalance());
        var originalRefundAmount = BigDecimal.valueOf(refundAmount.get());
        // 充值记录
        var payLogs = payLogService.lambdaQuery().eq(PayLog::getUserId, userId).eq(PayLog::getTradeState, PayLog.STATUS_充值成功).orderByDesc(PayLog::getSuccessTime).list();
        // 历史退款金额
        var refundsAmount = refundLogService.lambdaQuery().eq(RefundLog::getUserId, userId).list().stream().mapToInt(RefundLog::getRefund).sum();
        // 充值金额-历史退款金额不足以支付余额退款
        if (!CommUtil.isEmptyOrNull(payLogs)) {
            var total = payLogs.stream().mapToInt(PayLog::getTotal).sum();
            if ((total - refundsAmount) < refundAmount.get()) {
                LOGGER.error("用户：{},历史充值金额：{}，不足以支付余额退款金额：{}", userId, total - refundsAmount, account.getBalance());
                throw new BusinessException("充值金额不足以支付余额退款");
            }
        }

        // 最后一次的充值金额可以覆盖退款金额
        if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() >= refundAmount.get()) {
            // 退款日志
            var refundLog = new RefundLog().setUserId(payLogs.get(0).getUserId()).setOutTradeNo(payLogs.get(0).getOutTradeNo())
                    .setTotal(payLogs.get(0).getTotal())
                    .setRefund(refundAmount.get())
                    .setDiscountAmount(account.getDiscountAmount())
                    .setOutRefundNo(OrderUtils.getOrderNo());
            refundLogService.save(refundLog);
        } else if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() < refundAmount.get()) {
            // 最后一次的充值金额不能覆盖退款金额，拆分成多笔退款
            int amount = 0;
            // 用来退款的充值记录
            var refundPayLogs = new ArrayList<PayLog>();
            for (PayLog payLog : payLogs) {
                amount += payLog.getTotal();
                refundPayLogs.add(payLog);
                if (amount >= refundAmount.get()) {
                    break;
                }
            }
            // 需要退款的记录数量
            var size = refundPayLogs.size();

            var refundLogList = new ArrayList<RefundLog>(size);
            var newRefundLogList = new ArrayList<RefundLog>(size);

            refundPayLogs.forEach(LambadaTools.forEachWithIndex((item, index) -> {
                // 如果不是最后一笔，金额全退，最后一笔退剩余的金额
                if (index < size - 1) {
                    refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
                            .setTotal(item.getTotal()).setRefund(item.getTotal()).setOutRefundNo(OrderUtils.getOrderNo()));
                    refundAmount.addAndGet(-item.getTotal());
                } else {
                    refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
                            .setTotal(item.getTotal()).setRefund(refundAmount.get()).setOutRefundNo(OrderUtils.getOrderNo()));
                }
            }));

            // 不可退金额按退款金额比例放入每笔退款中
            refundLogList.forEach(LambadaTools.forEachWithIndex((refundLog, index) -> {
                int discountAmount;
                if (index < size - 1) {
                    discountAmount = BigDecimal.valueOf(account.getDiscountAmount()).multiply((BigDecimal.valueOf(refundLog.getRefund()).divide(originalRefundAmount, 2, RoundingMode.HALF_UP))).intValue();
                } else {
                    // 前面存在精度误差，最后一个元素用总数相减最精确
                    discountAmount = account.getDiscountAmount() - newRefundLogList.stream().mapToInt(RefundLog::getDiscountAmount).sum();
                }
                refundLog.setDiscountAmount(discountAmount);
                newRefundLogList.add(index, refundLog);
            }));

            newRefundLogList.forEach(item -> item.setReason(reason));

            refundLogService.saveBatch(newRefundLogList);
        } else {
            // 退款异常
            LOGGER.error("退款异常：userId：{}，退款金额：{}，payLogs：{}", userId, originalRefundAmount, payLogs);
            throw new BusinessException("退款异常");
        }
    }


    /**
     * 处理退款
     *
     * @return
     */
    @Override
    public void wxRefund(long refundLogId) {
        // 通过退款申请id获取退款申请记录
        var refundLog = refundLogService.getById(refundLogId);

        // 钱包流水
        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_退款)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(refundLog.getUserId())
                .setAmount(refundLog.getRefund())
                .setOrderNo(refundLog.getOutRefundNo());
        walletDetailService.save(walletDetail);

        CreateRequest request = new CreateRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        // 原始第三方订单号
        request.setOutTradeNo(refundLog.getOutTradeNo());
        // 退款订单号
        request.setOutRefundNo(refundLog.getOutRefundNo());
        // 退款原因
        request.setReason("用户申请退款");
        // 回调通知地址
        request.setNotifyUrl(conf.getRefundNotifyUrl());
        var amount = new AmountReq();
        // 退款金额
        amount.setRefund((long) refundLog.getRefund());
        amount.setTotal((long) refundLog.getTotal());
        amount.setCurrency(refundLog.getCurrency());
        request.setAmount(amount);
        try {
            var refund = refundService.create(request);
            refundLog.setChannel(refund.getChannel().name());
            refundLog.setFundsAccount(refund.getFundsAccount().name());
            refundLog.setCurrency(refund.getAmount().getCurrency());
            refundLog.setAdminUserId(StpUtil.getLoginIdAsLong());
            refundLog.setAdminUsername(StpUtil.getSession().getString("username"));
            refundLog.setStatus(RefundLog.STATUS_退款处理中);
            refundLogService.updateById(refundLog);
        } catch (ServiceException e) {
            LOGGER.error("微信退款异常：{}", e.getErrorMessage());
            throw new BusinessException("微信退款异常:" + e.getErrorMessage());
        }
    }

    @Override
    public void batchWxRefund(List<Long> refundLogIds) {
        if (refundLogIds == null || refundLogIds.isEmpty()) {
            throw new BusinessException("退款记录ID不能为空");
        }
        StringBuilder errors = new StringBuilder();
        int successCount = 0;
        for (Long refundLogId : refundLogIds) {
            try {
                wxRefund(refundLogId);
                successCount++;
            } catch (Exception e) {
                LOGGER.error("批量退款处理失败，退款记录ID：{}，错误：{}", refundLogId, e.getMessage());
                errors.append("退款记录").append(refundLogId).append("处理失败：").append(e.getMessage()).append("；");
            }
        }
        if (successCount == 0 && errors.length() > 0) {
            throw new BusinessException(errors.toString());
        }
        if (errors.length() > 0) {
            LOGGER.info("批量退款部分成功：成功{}笔，失败原因：{}", successCount, errors);
        }
    }

    /**
     * 查询单笔退款（通过商户退款单号）
     *
     * @return
     */
    @Override
    public Refund queryByOutRefundNo(String outRefundNo) {
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setOutRefundNo(outRefundNo);
        // 调用接口
        return refundService.queryByOutRefundNo(request);
    }


    /**
     * 微信退款结果通知
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<Object> wxRefundNotify(HttpServletRequest request) {
        var notifyRes = handleWxNotify(request);

        try {
            // 以支付通知回调为例，验签、解密并转换成 RefundNotification
            RefundNotification refundNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], RefundNotification.class);
            LOGGER.info("微信退款回调{}：验签解密完毕，数据：\n{}", notifyRes[2], refundNotification);

            // 退款日志在申请时插入，接收通知时更新
            var refundLog = refundLogService.lambdaQuery().eq(RefundLog::getOutRefundNo, refundNotification.getOutRefundNo()).one();
            if (refundLog == null) {
                LOGGER.error("微信退款回调：未找到退款记录，outRefundNo={}", refundNotification.getOutRefundNo());
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "退款记录不存在"));
            }
            // 幂等快路径：已处理成功直接返回
            if (RefundLog.STATUS_退款成功.equals(refundLog.getStatus())) {
                return ResponseEntity.status(HttpStatus.OK).build();
            }

            DateTime dt = DateUtil.parse(refundNotification.getSuccessTime());
            LocalDateTime successTime = LocalDateTimeUtil.of(dt);

            if (RefundLog.STATUS_退款成功.equals(refundNotification.getRefundStatus().name())) {
                // 资金流水必须在申请退款时已存在；检查放在门闩之前，避免门闩已置成功但后续失败造成不一致
                var walletDetail = walletDetailService.getWalletDetailByOrderNo(refundNotification.getOutRefundNo(), WalletDetail.TYPE_退款);
                if (walletDetail == null) {
                    LOGGER.error("微信退款回调：未找到退款资金流水，outRefundNo={}", refundNotification.getOutRefundNo());
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "退款流水不存在"));
                }

                // 幂等门闩：条件更新（status <> 已成功），并发重复回调第二个事务更新 0 行直接跳过
                boolean claimed = refundLogService.lambdaUpdate()
                        .set(RefundLog::getRefundId, refundNotification.getRefundId())
                        .set(RefundLog::getTransactionId, refundNotification.getTransactionId())
                        .set(RefundLog::getUserReceivedAccount, refundNotification.getUserReceivedAccount())
                        .set(RefundLog::getSuccessTime, successTime)
                        .set(RefundLog::getStatus, refundNotification.getRefundStatus().name())
                        .set(RefundLog::getTotal, refundNotification.getAmount().getTotal().intValue())
                        .set(RefundLog::getRefund, refundNotification.getAmount().getRefund().intValue())
                        .eq(RefundLog::getId, refundLog.getId())
                        .ne(RefundLog::getStatus, RefundLog.STATUS_退款成功)
                        .update();
                if (!claimed) {
                    return ResponseEntity.status(HttpStatus.OK).build();
                }

                // 冻结金额扣减此次（退款金额+优惠金额）,优惠金额字段减去申请退款时的优惠金额
                var account = accountService.getAccountByUserId(refundLog.getUserId());
                var refundAmount = refundNotification.getAmount().getRefund().intValue();
                accountService.lambdaUpdate()
                        .setSql("frozen_amount = (frozen_amount - {0}), discount_amount = (discount_amount - {1})",
                                refundAmount, refundLog.getDiscountAmount())
                        .eq(Account::getUserId, refundLog.getUserId()).update();

                // 更新资金流水
                // 注意：此时 balance 已在 applyWxRefund 阶段清零，
                // beforeBalance 应反映退款前余额（即退款金额），afterBalance 为 0
                walletDetailService.lambdaUpdate()
                        .set(WalletDetail::getStatus, WalletDetail.STATUS_已确认)
                        .set(WalletDetail::getTransactionId, refundNotification.getTransactionId())
                        .set(WalletDetail::getTransactionTime, successTime)
                        .set(WalletDetail::getAmount, refundAmount)
                        .set(WalletDetail::getBeforeBalance, account.getBalance() + refundAmount)
                        .set(WalletDetail::getAfterBalance, account.getBalance())
                        .eq(WalletDetail::getId, walletDetail.getId()).update();
                LOGGER.info("微信退款回调{}：业务处理结束", notifyRes[2]);

                // V2 结算方案：记录退款分账流水，结算时扣除
                var stationId = KymCache.INSTANCE.getUserStationId(refundLog.getUserId());
                var refundSplit = new SplitRecord()
                        .setFromStationId(stationId)
                        .setToStationId(stationId)
                        .setTradeNo(refundNotification.getTransactionId())
                        .setAmount(refundAmount)
                        .setType(SplitRecord.TYPE_REFUND);
                splitRecordService.save(refundSplit);

                return ResponseEntity.status(HttpStatus.OK).build();
            } else {
                // 退款失败
                LOGGER.error("微信退款失败，用户id:{}，退款状态：{}，退款结果通知详情：{}", refundLog.getUserId(), refundNotification.getRefundStatus().name(), refundNotification);
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "退款处理异常"));
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信退款通知验签失败", e);
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
        }
    }

    //================================================================发票=====================================================================


}
