package com.ycwl.basic.service.mobile.impl;


import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.utils.StringUtils;
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.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.TransactionAmount;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.config.WechatConfig;
import com.ycwl.basic.constant.HttpConstant;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.constant.WeiXinConstant;
import com.ycwl.basic.enums.BizCodeEnum;
import com.ycwl.basic.enums.OrderStateEnum;
import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.exception.AppException;
import com.ycwl.basic.mapper.OrderMapper;
import com.ycwl.basic.mapper.PaymentMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.order.req.OrderUpdateReq;
import com.ycwl.basic.model.pc.payment.entity.PaymentEntity;
import com.ycwl.basic.model.wx.WXPayOrderReqVO;
import com.ycwl.basic.model.wx.WxPayRespVO;
import com.ycwl.basic.model.wx.WxchatCallbackSuccessData;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.wechat.pay.java.core.http.Constant.*;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;
import static com.ycwl.basic.constant.WeiXinConstant.*;

/**
 * @Author: songmingsong
 * @CreateTime: 2024-12-05
 * @Description: 微信支付实现
 * @Version: 1.0
 */
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private PaymentMapper paymentMapper;
    @Autowired
    private StatisticsMapper statisticsMapper;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private OrderBiz orderBiz;
    @Autowired
    private OrderMapper orderMapper;

    @Override
    public WxPayRespVO createOrder(WXPayOrderReqVO req) {
        PaymentEntity entity = new PaymentEntity();
        entity.setOrderId(req.getOrderSn());
        entity.setMemberId(req.getMemberId());
        entity.setPayPrice(new BigDecimal(BigInteger.valueOf(req.getTotalPrice()), 2));
        Long entityId = paymentMapper.addGetId(entity);
        try {
            // 使用自动更新平台证书的RSA配置
            Config config = getInstance(wechatConfig);
            // 构建service
            JsapiService service = new JsapiService.Builder().config(config).build();

            // request.setXxx(val)设置所需参数，具体参数可见Request定义
            PrepayRequest request = new PrepayRequest();
            Amount amount = new Amount();
            amount.setTotal(req.getTotalPrice());
            request.setAmount(amount);
            request.setAppid(wechatConfig.getMiniProgramAppId());
            request.setMchid(wechatConfig.getMchId());
            request.setDescription(req.getGoodsName());
            request.setNotifyUrl(wechatConfig.getPayNotifyUrl());
            request.setOutTradeNo(req.getOrderSn().toString());
            request.setDescription(req.getDescription());

            Payer payer = new Payer();
            payer.setOpenid(req.getOpenId());
            request.setPayer(payer);
//            SettleInfo settleInfo = new SettleInfo();
//            settleInfo.setProfitSharing(true);
//            request.setSettleInfo(settleInfo);

            // 调用下单方法，得到应答
            PrepayResponse response = service.prepay(request);
            WxPayRespVO vo = new WxPayRespVO();
            Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
            vo.setTimeStamp(timeStamp);
            String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
            vo.setNonceStr(substring);
            String signatureStr = Stream.of(wechatConfig.getMiniProgramAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
                    .collect(Collectors.joining("\n", "", "\n"));
            String sign = WXPayUtil.getSign(signatureStr, wechatConfig.getKeyPath());
            vo.setPaySign(sign);
            vo.setSignType("RSA-SHA256");
            vo.setPrepayId("prepay_id=" + response.getPrepayId());
            return vo;
        } catch (ServiceException e) {
            JSONObject parse = JSONObject.parseObject(e.getResponseBody());
            throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, parse.getString(HttpConstant.Message));
        } catch (Exception e) {
            throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_FAILED, e.toString());
        }
    }

    @Override
    public void payNotify(HttpServletRequest request) {
        try {
            // 读取请求体的信息
            ServletInputStream inputStream = request.getInputStream();
            StringBuffer stringBuffer = new StringBuffer();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s;
            // 读取回调请求体
            while ((s = bufferedReader.readLine()) != null) {
                stringBuffer.append(s);
            }
            String s1 = stringBuffer.toString();
            log.warn("微信支付回调：{}", s1);
            String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
            String nonce = request.getHeader(WECHAT_PAY_NONCE);
            String signType = request.getHeader(WeiXinConstant.WECHATPAY_SIGNATURE_TYPE);
            String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(wechatConfig.getMchId())
                    .privateKey(wechatConfig.getKeyPath())
                    .merchantSerialNumber(wechatConfig.getMchSerialNo())
                    .apiV3Key(wechatConfig.getApiV3())
                    .build();
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(config);
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    // 若未设置signType，默认值为 WECHATPAY2-SHA256-RSA2048
                    .signType(signType)
                    .body(s1)
                    .build();
            Transaction parse = parser.parse(requestParam, Transaction.class);
            log.info("[微信支付]parse = {}", parse);

            // 更新订单信息
            new Thread(() -> {
                long orderId = Long.parseLong(parse.getOutTradeNo());
                switch (parse.getTradeState()) {
                    case SUCCESS:
                        orderBiz.paidOrder(orderId);
                        break;
                    case NOTPAY:
                    case CLOSED:
                    case REVOKED:
                        orderBiz.cancelOrder(orderId);
                        break;
                    case REFUND:
                        orderBiz.refundOrder(orderId);
                        break;
                }
            }).start();
        } catch (Exception e) {
            throw new AppException(BizCodeEnum.ADVANCE_PAYMENT_CALLBACK_FAILED, e.toString());
        }
    }

    @Override
    public WxchatCallbackSuccessData queryPay(Long orderId) {
        WxchatCallbackSuccessData wxchatCallbackSuccessData = new WxchatCallbackSuccessData();
        QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
        queryRequest.setMchid(wechatConfig.getMchId());
        queryRequest.setOutTradeNo(orderId.toString());
        Config config = getInstance(wechatConfig);
        // 构建service
        JsapiService service = new JsapiService.Builder().config(config).build();
        try {
            com.wechat.pay.java.service.payments.model.Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
            String successTime = transaction.getSuccessTime();

            wxchatCallbackSuccessData.setOrderId(orderId.toString());
            wxchatCallbackSuccessData.setSuccessTime(successTime);
            wxchatCallbackSuccessData.setTradetype(WeiXinConstant.getDescriptionType(transaction.getTradeType()));
            wxchatCallbackSuccessData.setTradestate(WeiXinConstant.getDescriptionState(transaction.getTradeState()));
            TransactionAmount amount = transaction.getAmount();
            Integer total = amount.getTotal();
            wxchatCallbackSuccessData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
        } catch (ServiceException e) {
            // API返回失败, 例如ORDER_NOT_EXISTS
            log.error("[微信退款] code={}, message={}", e.getErrorCode(), e.getErrorMessage());
            log.error("[微信退款] Response_body={}", e.getResponseBody());
        }
        return wxchatCallbackSuccessData;
    }

    @Override
    public Boolean refundOrder(String orderId) {
        OrderEntity order = orderRepository.getOrder(Long.parseLong(orderId));
        BigDecimal payPrice = order.getPayPrice();
        long priceInCents = payPrice.multiply(new BigDecimal(NumberConstant.HUNDRED)).longValue(); // 转换为分（int）

        Config config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(wechatConfig.getMchId())
                        // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥，商户私钥会用来生成请求的签名
                        .privateKey(wechatConfig.getKeyPath())
                        .merchantSerialNumber(wechatConfig.getMchSerialNo())
                        .apiV3Key(wechatConfig.getApiV3())
                        .build();
        RefundService service = new RefundService.Builder().config(config).build();
        CreateRequest request = new CreateRequest();
        request.setNotifyUrl(wechatConfig.getRefundNotifyUrl());
        AmountReq amountReq = new AmountReq();
        amountReq.setTotal(priceInCents);
        amountReq.setRefund(priceInCents);
        amountReq.setCurrency(WECHATPAY_CURRENCY_CNY);
        request.setAmount(amountReq);
        request.setOutTradeNo(orderId);
        request.setOutRefundNo(SnowFlakeUtil.getId());
        Refund refundResult = service.create(request);
        if (refundResult.getStatus() == SUCCESS) {
            OrderUpdateReq orderUpdateReq = new OrderUpdateReq();
            orderUpdateReq.setId(Long.parseLong(orderId));
            orderUpdateReq.setRefundStatus(OrderStateEnum.REFUNDED.getType());
            orderUpdateReq.setRefundAt(new Date());
            orderMapper.update(orderUpdateReq);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean refundNotify(String refundResult) throws IOException, GeneralSecurityException {
        // 转为map格式
        Map<String, String> jsonMap = JSONObject.parseObject(refundResult, Map.class);

        /*
         * 退款成功后返回一个加密字段resource,以下为解密
         *  解密需要从resource参数中，获取到ciphertext，nonce，associated_data这三个参数进行解密
         */
        String resource = JSONObject.toJSONString(jsonMap.get(REFUNDS_RESOURCE));
        JSONObject object = JSONObject.parseObject(resource);

        String ciphertext = String.valueOf(object.get(REFUNDS_CIPHERTEXT));
        String nonce = String.valueOf(object.get(REFUNDS_NONCE));
        String associated_data = String.valueOf(object.get(REFUNDS_ASSOCIATED_DATA));

        String resultStr = decryptToString(associated_data.getBytes(String.valueOf(StandardCharsets.UTF_8)),
                nonce.getBytes(String.valueOf(StandardCharsets.UTF_8)),
                ciphertext);
        Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);

        String refund_status = reqInfo.get(REFUNDS_REFUND_STATUS);// 退款状态
        String out_trade_no = reqInfo.get(WECHATPAY_OUT_TRADE_NO); // 订单号

        if (!StringUtils.isEmpty(refund_status) && WECHATPAY_SUCCESS.equals(refund_status)) {
            long orderId = Long.parseLong(out_trade_no);
            orderBiz.refundOrder(orderId);

            OrderEntity order = orderRepository.getOrder(orderId);
            StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq();
            statisticsRecordAddReq.setMemberId(order.getMemberId());
            statisticsRecordAddReq.setType(StatisticEnum.REFUND.code);
            statisticsRecordAddReq.setScenicId(order.getScenicId());
            statisticsRecordAddReq.setMorphId(orderId);
            statisticsMapper.addStatisticsRecord(statisticsRecordAddReq);

            log.info("[微信退款回调]退款成功");
            return true;
        } else {
            log.error("[微信退款回调]退款失败");
            return false;
        }
    }

    @Override
    public void closeOrder(String orderId) {
        CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
        closeOrderRequest.setOutTradeNo(orderId);
        closeOrderRequest.setMchid(wechatConfig.getMchId());
        Config config = getInstance(wechatConfig);
        // 构建service
        JsapiService service = new JsapiService.Builder().config(config).build();
        service.closeOrder(closeOrderRequest);
    }


    /**
     * 退款回调  解密数据
     *
     * @param associatedData
     * @param nonce
     * @param ciphertext
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(wechatConfig.getApiV3().getBytes(), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }


    /**
     * 生成退款请求token
     *
     * @param method
     * @param orderId
     * @param body
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws SignatureException
     */
    public String getToken(String method, String orderId, String body) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, SignatureException {
        String nonceStr = WXPayUtil.generateNonceStr();
        long timestamp = System.currentTimeMillis() / NumberConstant.THOUSAND; // 生成时间戳

        String parameter = method + "\n"
                + REFUNDS_URi + orderId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";

        // 对参数进行加密
        byte[] bytes = parameter.getBytes(String.valueOf(StandardCharsets.UTF_8));
        Signature sign = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = PemUtil.loadPrivateKeyFromString(wechatConfig.getKeyPath());  // privateKeyPath是商户证书密钥的位置apiclient_key.pem
        sign.initSign(privateKey);   // 商户密钥文件路径
        sign.update(bytes);
        String signature = Base64.getEncoder().encodeToString(sign.sign());

        // 获取token
        String token = "mchid=\"" + wechatConfig.getMchId() + "\","      // 商户号
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + wechatConfig.getMchSerialNo() + "\"," // merchantSerialNumber是微信支付中申请的证书序列号
                + "signature=\"" + signature + "\"";

        return REFUNDS_SCHEMA + token;
    }

    /**
     * 一个商户号只能初始化一个配置，否则会因为重复的下载任务报错
     */
    private static class Holder {
        private static volatile Config instance;

        static void init(WechatConfig wechatConfig) {
            instance = new RSAAutoCertificateConfig.Builder()
                    .merchantId(wechatConfig.getMchId())
                    .privateKey(wechatConfig.getKeyPath())
                    .merchantSerialNumber(wechatConfig.getMchSerialNo())
                    .apiV3Key(wechatConfig.getApiV3())
                    .build();
        }
    }

    public static Config getInstance(WechatConfig wechatConfig) {
        if (Holder.instance == null) {
            synchronized (Holder.class) {
                if (Holder.instance == null) {
                    Holder.init(wechatConfig);
                }
            }
        }
        return Holder.instance;
    }
}
