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.baomidou.dynamic.datasource.annotation.DS;
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.HttpUtil;
import com.kym.common.utils.LambadaTools;
import com.kym.common.utils.OrderUtils;
import com.kym.entity.miniapp.Account;
import com.kym.entity.miniapp.PayLog;
import com.kym.entity.miniapp.RefundLog;
import com.kym.entity.miniapp.WalletDetail;
import com.kym.entity.wechat.FaPiao;
import com.kym.entity.wechat.InvoiceBaseInfo;
import com.kym.entity.wechat.InvoiceNotification;
import com.kym.entity.wechat.TaxCodes;
import com.kym.service.miniapp.*;
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.ValidationException;
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.http.HttpServletRequest;
import lombok.SneakyThrows;
import okhttp3.Headers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
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 ChargeOrderService chargeOrderService;

    private final RefundLogService refundLogService;


    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService) {
        this.conf = conf;
        this.walletDetailService = walletDetailService;
        this.payLogService = payLogService;
        this.accountService = accountService;
        this.chargeOrderService = chargeOrderService;
        this.refundLogService = refundLogService;
    }

    /**
     * 商户订单号查询订单
     */
    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();
    }


    /**
     * 处理微信回调信息，验签解密
     *
     * @param request
     * @return array[0] RequestParam 请求参数
     * array[1] NotificationParser 通知解析器
     * array[2] nonce 随机数
     */
    @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");

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

        // request中获取body
        BufferedReader br = request.getReader();
        String str;
        var requestBody = new StringBuilder();
        while ((str = br.readLine()) != null) {
            requestBody.append(str);
        }

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

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

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

    /**
     * JSAPI支付下单
     */
    @Override
    public PrepayWithRequestPaymentResponse wxPay(JSONObject params) {
        if (!params.containsKey("amount") || params.getInteger("amount") == null || !(params.get("amount") instanceof Integer)) {
            throw new BusinessException(ResponseEnum.WX_PAY_AMOUNT_ERROR);
        }
        var rechargeAmount = params.getInteger("amount");
        var openid = StpUtil.getSession().getString("openid");
        var userId = StpUtil.getLoginIdAsLong();
        // 生成订单号
        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("K7充电生活充值");
        request.setNotifyUrl(conf.getNotifyUrl());
        request.setOutTradeNo(outTradeNo);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        // response包含了调起支付所需的所有参数，可直接用于前端调起支付
        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);
    }

    /**
     * 关闭订单
     *
     * @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 void wxNotify(HttpServletRequest request) {
        var notifyRes = handleWxNotify(request);

        try {
            // 以支付通知回调为例，验签、解密并转换成 Transaction
            Transaction transaction = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], Transaction.class);
            LOGGER.info("微信支付回调{}：验签解密完毕，数据：\n{}", notifyRes[2], transaction);
            // 判断是否已经接收处理过通知
            if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
                return;
            }

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

            // 钱包流水
            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo(), WalletDetail.TYPE_充值);
            if (walletDetail != null) {
                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
                walletDetail.setCurrency(transaction.getAmount().getCurrency());
                walletDetail.setAmount(transaction.getAmount().getTotal());
                walletDetail.setTransactionTime(successTime);
                walletDetailService.updateById(walletDetail);

                // 更新余额
                accountService.lambdaUpdate().setSql("balance = (balance+%d)".formatted(transaction.getAmount().getTotal()))
                        .eq(Account::getUserId, walletDetail.getUserId()).update();

                // 支付记录
                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());
                payLog.setTotal(transaction.getAmount().getTotal());
                payLog.setCurrency(transaction.getAmount().getCurrency());
                payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
                payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
                payLogService.save(payLog);
                LOGGER.info("微信支付回调{}：业务处理结束", notifyRes[2]);

            } else {
                LOGGER.error("微信支付通知处理异常，资金流水为空，回调信息：{}", transaction);
//                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
                throw new BusinessException("处理异常");
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信支付通知验签失败", e);
            throw new BusinessException("验签失败");
//            return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 申请退款
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyWxRefund() {
        var userId = StpUtil.getLoginIdAsLong();
        var chargeOrder = chargeOrderService.getChargingOrderByUserId(userId);
        if (chargeOrder != null) {
            throw new BusinessException("存在未完结的订单，请等待所有订单完结之后重试");
        }
        var account = accountService.getAccountByUserId(userId);
        if (account.getBalance() <= 0) {
            throw new BusinessException("账户余额不足，无需退款");
        }

        // 将余额转移至冻结余额
        accountService.lambdaUpdate().setSql(" frozen_amount = (frozen_amount + balance) ,balance = 0").eq(Account::getUserId, userId).update();

        // 余额作为退款金额
        AtomicInteger refundAmount = new AtomicInteger(account.getBalance());
        // 充值记录
        var payLogs = payLogService.lambdaQuery().eq(PayLog::getUserId, userId).eq(PayLog::getTradeState, RefundLog.STATUS_退款成功).orderByDesc(PayLog::getSuccessTime).list();
        // 充值金额综合不足以支付余额退款
        if (!CommUtil.isEmptyOrNull(payLogs)) {
            var total = payLogs.stream().mapToInt(PayLog::getTotal).sum();
            if (total < account.getBalance()) {
                LOGGER.error("用户：{},充值总金额：{}，不足以支付余额退款金额：{}", userId, total, account.getBalance());
                throw new BusinessException("充值金额不足以支付余额退款");
            }
        }
        // 最后一次的充值金额可以覆盖退款金额
        if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() >= refundAmount.get()) {
            // 退款日志
            createRefundLog(payLogs.get(0));
        } 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();
            refundPayLogs.forEach(LambadaTools.forEachWithIndex((item, index) -> {
                // 如果不是最后一笔，金额全退，最后一笔退剩余的金额
                if (index < size) {
                    createRefundLog(item);
                    refundAmount.addAndGet(-item.getTotal());
                } else {
                    item.setTotal(refundAmount.get());
                    createRefundLog(item);
                }
            }));
        } else {
            // 退款异常
            LOGGER.error("退款异常：userId：{}，余额：{},payLogs:{}", userId, refundAmount.get(), payLogs);
            throw new BusinessException("退款异常");
        }
    }

    /**
     * 创建退款申请记录
     *
     * @param payLog
     */
    public void createRefundLog(PayLog payLog) {
        // 退款日志
        var refundLog = new RefundLog()
                .setUserId(payLog.getUserId())
                .setOutTradeNo(payLog.getOutTradeNo())
                .setOutRefundNo(OrderUtils.getOrderNo())
                .setTotal(payLog.getTotal())
                .setRefund(payLog.getTotal());
        refundLogService.save(refundLog);
    }


    /**
     * 处理退款
     *
     * @return
     */
    @DS("db-miniapp")
    @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);
        var refund = refundService.create(request);

        refundLog.setChannel(refund.getChannel().name());
        refundLog.setFundsAccount(refund.getFundsAccount().name());
        refundLog.setCurrency(refund.getAmount().getCurrency());
        refundLogService.updateById(refundLog);

    }

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


    /**
     * 微信退款结果通知
     *
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void 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.STATUS_退款成功.equals(refundLog.getStatus())) {
                return;
            }

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

            refundLog
                    .setRefundId(refundNotification.getRefundId())
                    .setTransactionId(refundNotification.getTransactionId())
                    .setUserReceivedAccount(refundNotification.getUserReceivedAccount())
                    .setSuccessTime(successTime)
                    .setStatus(refundNotification.getRefundStatus().name())
                    .setTotal(refundNotification.getAmount().getTotal().intValue())
                    .setRefund(refundNotification.getAmount().getRefund().intValue());
            refundLog.setCreateTime(LocalDateTimeUtil.of(DateUtil.parse(refundNotification.getCreateTime())));
            refundLogService.updateById(refundLog);

            if (RefundLog.STATUS_退款成功.equals(refundNotification.getRefundStatus().name())) {
                // 冻结金额扣减此次退款金额
                accountService.lambdaUpdate().setSql(" frozen_amount = (frozen_amount - %d)".formatted(refundNotification.getAmount().getRefund().intValue())).eq(Account::getUserId, refundLog.getUserId()).update();
                // 更新资金流水
                var walletDetail = walletDetailService.getWalletDetailByOrderNo(refundNotification.getOutRefundNo(), WalletDetail.TYPE_提现);
                walletDetail.setStatus(WalletDetail.STATUS_已确认)  //已确认
                        .setCurrency(refundNotification.getAmount().getCurrency())
                        .setAmount(refundNotification.getAmount().getRefund().intValue())
                        .setTransactionTime(successTime);
                walletDetailService.updateById(walletDetail);

                LOGGER.info("微信退款回调{}：业务处理结束", notifyRes[2]);
            } else {
                // 退款失败
                LOGGER.error("微信退款失败，用户id:{}，退款状态：{} \n 退款结果通知详情：{}", refundLog.getUserId(), refundNotification.getRefundStatus().name(), refundNotification);
                throw new BusinessException("处理异常");
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信退款通知验签失败", e);
            throw new BusinessException("验签失败");
        }
    }


    /**
     * 获取商户开票基础信息
     */
    private InvoiceBaseInfo baseInformation() {
        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/base-information";
        Headers headers = Headers.of(
                "Authorization", "",
                "Accept", "application/json",
                "Content-Type", "application/json"
        );
        var res = HttpUtil.post(url, headers);
        return JSONObject.parseObject(res).toJavaObject(InvoiceBaseInfo.class);
    }


    /**
     * 获取商户可开具的商品和服务税收分类编码对照表
     */
    private TaxCodes.TaxCodeItem getTaxCodes() {
        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/tax-codes";
        Headers headers = Headers.of(
                "Authorization", "",
                "Accept", "application/json"
        );
        var params = """
                {
                    "offset" : %d,
                    "limit" ：%d
                }
                """.stripIndent().formatted(0, 100);
        var res = HttpUtil.post(url, params, headers);
        return JSONObject.parseObject(res).toJavaObject(TaxCodes.TaxCodeItem.class);
    }


    /**
     * 申请发票
     */
    public void applyInvoice() {
        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications";
        Headers headers = Headers.of(
                "Authorization", "",
                "Accept", "application/json",
                "Content-Type", "application/json",
                "Wechatpay-Serial", "6A45EEB068369430B2FFD45EA29F641A8E18165F"
        );

        var invoiceId = OrderUtils.getOrderNo();
        var fapiao = new FaPiao();
        var res = HttpUtil.post(url, JSONObject.toJSONString(fapiao), headers);
    }

    /**
     * 微信开票结果通知
     *
     * @param request
     * @return
     */
    void wxInvoiceNotify(HttpServletRequest request) {
        var notifyRes = handleWxNotify(request);
        try {
            InvoiceNotification invoiceNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], InvoiceNotification.class);
            LOGGER.info("微信开具发票结果通知回调{}：验签解密完毕，数据：\n{}", notifyRes[2], invoiceNotification);
            // TODO: 2023-09-17 业务逻辑

        }  catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信开具发票结果通知验签失败", e);
            throw new BusinessException("验签失败");
        }
    }


}