package com.heyu.api.jsapi;

// 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/merchant/4014931831

import com.alibaba.fastjson.JSON;
import com.heyu.api.alibaba.request.mm.order.ProfitSharingDTO;
import com.heyu.api.data.dao.vv.VvOutTradeNoDao;
import com.heyu.api.data.dao.vv.VvReverseOrderDao;
import com.heyu.api.data.dao.vv.VvTradeOrderDao;
import com.heyu.api.data.entity.vv.VvOutTradeNoEntity;
import com.heyu.api.data.entity.vv.VvReverseOrderEntity;
import com.heyu.api.data.entity.vv.VvTradeOrderEntity;
import com.heyu.api.data.utils.BigDecimalUtil;
import com.heyu.api.data.utils.StringUtils;
import com.heyu.api.jsapi.dto.close.CloseOrderRequest;
import com.heyu.api.jsapi.dto.pay.*;
import com.heyu.api.jsapi.dto.profit.sharing.CreateOrderReceiver;
import com.heyu.api.jsapi.dto.profit.sharing.CreateOrderRequest;
import com.heyu.api.jsapi.dto.profit.sharing.OrdersEntity;
import com.heyu.api.jsapi.dto.refund.AmountReq;
import com.heyu.api.jsapi.dto.refund.CreateRequest;
import com.heyu.api.jsapi.dto.refund.Refund;
import com.heyu.api.jsapi.dto.refund.ReqFundsAccount;
import com.heyu.api.jsapi.dto.refund.query.QueryByOutRefundNoRequest;
import com.heyu.api.jsapi.utils.PemUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JSAPI下单
 */
@Component
@Slf4j
public class JsapiPrepay {

    @Value("${eb.config.weixin.pay.appid}")
    private String appid;

    @Value("${eb.config.weixin.pay.mchid}")
    private String mchid;

    @Value("${eb.config.weixin.pay.certificateSerialNo}")
    private String certificateSerialNo;

    private PrivateKey privateKey;

    private String wechatPayPublicKeyId;

    private PublicKey wechatPayPublicKey;


    @Autowired
    private VvTradeOrderDao vvTradeOrderDao;

    @Autowired
    private VvReverseOrderDao  vvReverseOrderDao;


    @Autowired
    private VvOutTradeNoDao vvOutTradeNoDao;



    @Value("${eb.config.weixin.pay.notifyUrl}")
    private String notifyUrl;



    @Value("${eb.config.weixin.pay.refundNotifyUrl}")
    private String refundNotifyUrl;

    public JsapiPrepay() {

    }

    public JsapiPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
        this.mchid = mchid;
        this.certificateSerialNo = certificateSerialNo;
        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
        this.wechatPayPublicKeyId = wechatPayPublicKeyId;
        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
    }

    @SneakyThrows
    public static String getSign(String signatureStr, String privateKey) {
        String replace = privateKey.replace("\\n", "\n");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace);
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    /***
     * https://pay.weixin.qq.com/doc/v3/merchant/4012791897
     * JSAPI/小程序下单
     */
    public DirectAPIv3JsapiPrepayResponse prePay(String outTradeNo,
                                                 Long amount,
                                                 String openid) {
        DirectAPIv3JsapiPrepayRequest request = new DirectAPIv3JsapiPrepayRequest();
        request.setAppid(appid);
        request.setMchid(mchid);
        request.setNotifyUrl(notifyUrl);
        request.setDescription(System.currentTimeMillis() + "");
        request.setOutTradeNo(outTradeNo );
        request.setTimeExpire(WXPayUtility.generateExpireTime()); // 2025-11-05T21:02:16+08:00
        request.setAttach("自定义数据说明");
        request.setGoodsTag("WXG");
        request.setSupportFapiao(false);
        CommonAmountInfo commonAmountInfo = new CommonAmountInfo();
        commonAmountInfo.setTotal(amount);
        commonAmountInfo.setCurrency("CNY");
        request.setAmount(commonAmountInfo);

        JsapiReqPayerInfo payer = new JsapiReqPayerInfo();
        payer.setOpenid(openid);
        request.setPayer(payer);
        return doPay(request);
    }

    /***
     * 商户订单号查询订单
     *
     * https://pay.weixin.qq.com/doc/v3/merchant/4012791900
     */
    public DirectAPIv3QueryResponse queryOrder(String transactionId) {
        QueryByWxTradeNoRequest request = new QueryByWxTradeNoRequest();
        request.transactionId = transactionId;
        request.mchid = mchid;
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "GET";
        String uri = "/v3/pay/transactions/id/{transaction_id}";
        uri = uri.replace("{transaction_id}", 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;
        }
        log.info("JsapiPrepay queryOrder uri:{},params:{}",uri,JSON.toJSONString(args));
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
        reqBuilder.method(METHOD, null);
        Request httpRequest = reqBuilder.build();
        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            log.info("JsapiPrepay queryOrder respBody = " + respBody);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);

                // 从HTTP应答报文构建返回数据
                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);
        }
    }

    /**
     * 关闭订单
     * https://pay.weixin.qq.com/doc/v3/merchant/4012791901
     */
    public void close(Long tradeOrderId) {
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "POST";
        String PATH = "/v3/pay/transactions/out-trade-no/{out_trade_no}/close";
        CloseOrderRequest request = new CloseOrderRequest();
        request.setOutTradeNo(tradeOrderId + "");
        request.setMchid(mchid);
        String uri = PATH;
        uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.outTradeNo));
        String reqBody = WXPayUtility.toJson(request);
        log.info("JsapiPrepay close reqBody = " + reqBody);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();

        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            log.info("JsapiPrepay close respBody = " + respBody);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                return;
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }


    /****
     * https://pay.weixin.qq.com/doc/v3/merchant/4012791903
     * 退款申请
     */
    public Refund refund(Long tradeOrderId, Long reverseOrderId) {
        VvTradeOrderEntity vvTradeOrderEntity = vvTradeOrderDao.selectVvTradeOrderById(tradeOrderId);
        VvReverseOrderEntity vvReverseOrderEntity = vvReverseOrderDao.selectVvReverseOrderById(reverseOrderId);
        CreateRequest request = new CreateRequest();
        request.transactionId = vvTradeOrderEntity.getTransactionId();      // 【微信支付订单号】 微信支付侧订单的唯一标识，订单支付成功后，查询订单和支付成功回调通知会返回该参数。transaction_id和out_trade_no必须二选一进行传参。


        VvOutTradeNoEntity vvOutTradeNo = new  VvOutTradeNoEntity();
        vvOutTradeNoDao.insertOrUpdateVvOutTradeNo(vvOutTradeNo);

        vvReverseOrderEntity.setOutRefundNo(vvOutTradeNo.getId()+"");
        vvReverseOrderDao.updateVvReverseOrderById(vvReverseOrderEntity);


        request.outTradeNo = vvTradeOrderEntity.getOutTradeNo() + "";      // 【商户订单号】 商户下单时传入的商户系统内部订单号。transaction_id和out_trade_no必须二选一进行传参。
        request.outRefundNo = vvReverseOrderEntity.getOutRefundNo() + ""; //【商户退款单号】 商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一商户退款单号多次请求只退一笔。不可超过64个字节数。
        request.reason = vvReverseOrderEntity.getRefundReason();  // 【退款原因】 若商户传了退款原因，该原因将在下发给用户的退款消息中显示，具体展示可参见退款通知UI示意图。请注意：1、该退款原因参数的长度不得超过80个字节；2、当订单退款金额小于等于1元且为部分退款时，退款原因将不会在消息中体现。
        request.notifyUrl = refundNotifyUrl;   // 【退款结果回调url】 异步接收微信支付退款结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。 如果传了该参数，则商户平台上配置的回调地址（商户平台-交易中心-退款管理-退款配置）将不会生效，优先回调当前传的这个地址。
        /**
         * AVAILABLE: 仅对旧资金流商户适用(请参考旧资金流介绍区分)，传此枚举指定从可用余额账户出资，否则默认使用未结算资金退款。
         * UNSETTLED: 仅对出行预付押金退款适用，指定从未结算资金出资。
         */
        request.fundsAccount = ReqFundsAccount.AVAILABLE;   // 【退款资金来源】 若传递此参数则使用对应的资金账户退款。
        // 【金额信息】订单退款金额信息
        request.amount = new AmountReq();

        Long refundAmount = BigDecimalUtil.multiply(vvReverseOrderEntity.getRefundAmount(), new BigDecimal(100)).longValue();
        Long tradeOrderAmount = BigDecimalUtil.multiply(vvTradeOrderEntity.getPromotionPrice(), new BigDecimal(100)).longValue();

        request.amount.refund =refundAmount; // 【退款金额】 退款金额，币种的最小单位，只能为整数，不能超过原订单支付金额。

        request.amount.total = tradeOrderAmount; // 必填  【原订单金额】 原支付交易的订单总金额，币种的最小单位，只能为整数
        request.amount.currency = "CNY";        // 必填 【退款币种】  符合ISO 4217标准的三位字母代码，固定传：CNY，代表人民币。
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "POST";
        String uri = "/v3/refund/domestic/refunds";
        log.info("JsapiPrepay refund params :{}", JSON.toJSONString(request));
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();
        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            log.info("JsapiPrepay order refund respBody:{}", respBody);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                // 从HTTP应答报文构建返回数据
                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);
        }
    }

    /***
     * https://pay.weixin.qq.com/doc/v3/merchant/4012524936
     * 分账
     */
    public OrdersEntity profitSharing(
            String transactionId,
            String outOrderNo,
            List<ProfitSharingDTO> profitSharingDTOList
    ) {

        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "POST";
        CreateOrderRequest request = new CreateOrderRequest();
        //【公众账号ID】 APPID是微信开放平台(移动应用)或微信公众平台(小程序、公众号)为开发者的应用程序提供的唯一标识。此处，可以填写这三种类型中的任意一种APPID，但请确保该appid与mchid有绑定关系。详见：开发必要参数说明，只有含PERSONAL_OPENID接收方的情况下该参数必填。
        request.appid = appid;

        // 微信订单号】 微信支付订单号
        request.transactionId = transactionId;

        // 【商户分账单号】 商户系统内部的分账单号，在商户系统内部唯一，同一分账单号多次请求等同一次。只能是数字、大小写字母_-|*@
        request.outOrderNo = outOrderNo;
        //【分账接收方列表】 分账接收方列表，可以设置出资商户作为分账接受方，最多可有50个分账接收方
        request.receivers = new ArrayList<>();

        for (ProfitSharingDTO profitSharingDTO : profitSharingDTOList) {
            CreateOrderReceiver receiversItem = new CreateOrderReceiver();
            /**
             *  type 　必填   string(32)
             *
             * 【分账接收方类型】
             *
             * MERCHANT_ID：商户号
             *
             * PERSONAL_OPENID：个人openid（用户在商户appid下的唯一标识，详见 OpenID获取）
             */
            receiversItem.type = "PERSONAL_OPENID";


            receiversItem.account = profitSharingDTO.getOpenid();

            /***
             *  name 　选填   string(1024)
             *
             * 【分账个人接收方姓名】
             *
             * 分账接收方类型是MERCHANT_ID时，是商户全称（必传），当商户是小微商户或个体户时，是开户人姓名
             *
             * 分账接收方类型是PERSONAL_OPENID时，是个人姓名（选传，传则会检查与 name 是否实名匹配，不匹配会拒绝分账请求）
             */
            //receiversItem.name = client.encrypt("name");
            /***
             * account 　必填   string(64)
             *
             * 【分账接收方帐号】
             *
             * 分账接收方类型为MERCHANT_ID时，分账接收方账号为商户号
             *
             * 分账接收方类型为PERSONAL_OPENID时，分账接收方账号为个人OpenID（由商户的AppID转换得到）
             */
            receiversItem.amount = profitSharingDTO.getAwardAmount().multiply(new BigDecimal(100)).longValue();

            if (StringUtils.isNotEmpty(profitSharingDTO.getBuyerName())) {
                receiversItem.description = profitSharingDTO.getBuyerName() + "profitSharing";
            } else {
                receiversItem.description = profitSharingDTO.getOpenid() + "openid";
            }

            request.receivers.add(receiversItem);

        }

        request.unfreezeUnsplit = true;
        String uri = "/v3/profitsharing/orders";
        String reqBody = WXPayUtility.toJson(request);

        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();

        // 发送HTTP请求
        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) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);
                // 从HTTP应答报文构建返回数据
                return WXPayUtility.fromJson(respBody, OrdersEntity.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }




    /***
     * https://pay.weixin.qq.com/doc/v3/merchant/4012791904
     *查询单笔退款（通过商户退款单号）
     *
     * 提交退款申请后，推荐每间隔1分钟调用该接口查询一次退款状态，若超过5分钟仍是退款处理中状态，建议开始逐步衰减查询频率(比如之后间隔5分钟、10分钟、20分钟、30分钟……查询一次)。
     *
     * 退款有一定延时，零钱支付的订单退款一般5分钟内到账，银行卡支付的订单退款一般1-3个工作日到账。
     *
     * 同一商户号查询退款频率限制为300qps，如返回FREQUENCY_LIMITED频率限制报错可间隔1分钟再重试查询。
     */
    public Refund queryRefund(String reverseOrderId) {
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "GET";
        String PATH = "/v3/refund/domestic/refunds/{out_refund_no}";
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.outRefundNo = reverseOrderId + "";  // 【商户退款单号】 商户申请退款时传入的商户系统内部退款单号。
        String uri = PATH;
        uri = uri.replace("{out_refund_no}", WXPayUtility.urlEncode(request.outRefundNo));
        log.info("JsapiPrepay queryRefund params :{},uri:{}", JSON.toJSONString(request),uri);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
        reqBuilder.method(METHOD, null);
        Request httpRequest = reqBuilder.build();

        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            log.info("JsapiPrepay queryRefund respBody:{}", respBody);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                        httpResponse.headers(), respBody);

                // 从HTTP应答报文构建返回数据
                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 doPay(DirectAPIv3JsapiPrepayRequest request) {
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "POST";
        String PATH = "/v3/pay/transactions/jsapi";
        String uri = PATH;
        log.info("JsapiPrepay doPay params :{}", JSON.toJSONString(request));
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
        reqBuilder.method(METHOD, requestBody);
        Request httpRequest = reqBuilder.build();
        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {

            String respBody = WXPayUtility.extractBody(httpResponse);
            log.info("JsapiPrepay doPay respBody:{}", respBody);
            if (respBody != null) {
            }
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, httpResponse.headers(), respBody);

                // 从HTTP应答报文构建返回数据
                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);
        }
    }
}
