package com.yida.data.common.core.utils;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Joiner;
import com.yida.data.common.core.common.ResultMsgType;
import com.yida.data.common.core.entity.constant.StringConstant;
import com.yida.data.common.core.entity.constant.UPayConstant;
import com.yida.data.common.core.entity.consume.DeptConsumeTerminalCache;
import com.yida.data.common.core.entity.pay.UPayOrder;
import com.yida.data.common.core.entity.pay.URefund;
import com.yida.data.common.core.entity.system.UPayConfig;
import com.yida.data.common.core.exception.FebsException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.TreeMap;

/**
 * 收钱吧支付工具类
 *
 * @author ZYJ
 * @date 2023/3/20
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UPayUtil {

    private static final String KEY_ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    /**
     * 跳转支付接口
     *
     * @param order       收钱吧支付订单类
     * @param terminalSn  终端序列号
     * @param terminalKey 终端密钥
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/3/21 17:06
     */
    public static String placeOrder(UPayOrder order, String terminalSn, String terminalKey) {
        try {
            JSONObject jsonObject = JSONUtil.createObj()
                    .set("terminal_sn", terminalSn)
                    .set("client_sn", order.getClientSn())
                    .set("total_amount", String.valueOf(NumberUtil.mul(order.getTotalAmount(), new BigDecimal(100)).intValue()))
                    .set("subject", StringUtils.isNotBlank(order.getSubject()) ? order.getSubject() : "充值缴费")
                    .set("operator", URLEncoder.encode(order.getOperator(), CharsetUtil.UTF_8))
                    .set("return_url", order.getReturnUrl())
                    .set("notify_url", order.getNotifyUrl());
            TreeMap<String, Object> paramMap = MapUtil.sort(jsonObject);
            // 处理请求参数
            String paramsStr = Joiner.on("&").withKeyValueSeparator("=").join(paramMap);
            // 签名
            String sign = getPayUrlSign(paramsStr, terminalKey);
            // 生成支付地址
            String url = UPayConstant.TO_PAY_URL + StringConstant.QUESTION_MARK + paramsStr + "&sign=" + sign;
            log.info("收钱吧支付地址: {}", url);
            return url;
        } catch (Exception e) {
            log.error("生成收钱吧支付地址失败", e);
            throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
        }
    }

    /**
     * 激活
     *
     * @param uPayConfig 收钱吧学校信息配置
     * @param deviceId   同一个app_id下唯一. 例：品牌名称+支付场景
     * @return com.yida.data.common.core.entity.consume.DeptConsumeTerminalCache
     * @author ZYJ
     * @date 2023/4/3 14:43
     */
    public static DeptConsumeTerminalCache active(UPayConfig uPayConfig, String deviceId) {
        // 封装请求参数
        JSONObject params = JSONUtil.createObj()
                // wap支付应用id
                .set("app_id", uPayConfig.getWapAppId())
                // 激活码内容
                .set("code", uPayConfig.getCode())
                // 设备唯一编号
                .set("device_id", deviceId);
        HttpResponse httpResponse = HttpRequest.post(UPayConstant.ACTIVATE)
                .header("Authorization", setSignHeader(uPayConfig.getVendorSn(), getSign(params.toString(), uPayConfig.getVendorKey())))
                .body(params.toString())
                .execute();
        // 返回数据
        log.info("激活接口返回数据: {}", httpResponse.body());
        JSONObject jsonObject = JSONUtil.parseObj(httpResponse.body());
        JSONObject bizResponse;
        // 激活接口返回数据: {"result_code":"200","biz_response":{"terminal_sn":"100053610028601010","terminal_key":"50f76a2c11121f22bd4d62e8350ec0f6","merchant_sn":"18956397746","merchant_name":"半夜鸡叫","store_sn":"00010101001200200046406","store_name":"半夜鸡叫"}}
        if (jsonObject.containsKey(UPayConstant.RESULT_CODE_NAME)
                && !UPayConstant.RESULT_SUCCESS_CODE.equals(jsonObject.getStr(UPayConstant.RESULT_CODE_NAME))) {
            log.error("激活设备失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                    jsonObject.getStr(UPayConstant.RESULT_CODE_NAME), jsonObject.getStr(UPayConstant.ERROR_CODE_NAME),
                    jsonObject.getStr(UPayConstant.ERROR_MESSAGE_NAME));
            throw new FebsException("激活设备失败");
        } else {
            bizResponse = jsonObject.getJSONObject(UPayConstant.BIZ_RESPONSE);
            if (bizResponse.containsKey(UPayConstant.BIZ_CODE_NAME)
                    && !UPayConstant.BIZ_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))) {
                log.error("激活设备失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                        bizResponse.getStr(UPayConstant.BIZ_CODE_NAME), bizResponse.getStr(UPayConstant.ERROR_CODE_NAME),
                        bizResponse.getStr(UPayConstant.ERROR_MESSAGE_NAME));
                throw new FebsException("激活设备失败");
            }
        }
        return DeptConsumeTerminalCache.builder()
                .terminalSn(bizResponse.getStr("terminal_sn"))
                .terminalKey(bizResponse.getStr("terminal_key"))
                .build();
    }

    /**
     * 订单查询
     *
     * @param clientSn    商户订单号
     * @param terminalSn  终端序列号
     * @param terminalKey 终端密钥
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/3/21 17:10
     */
    public static JSONObject queryOrder(String clientSn, String terminalSn, String terminalKey) {
        // 封装请求参数
        JSONObject params = JSONUtil.createObj()
                // 终端序列号
                .set("terminal_sn", terminalSn)
                // 商户订单号
                .set("client_sn", clientSn);
        HttpResponse httpResponse = HttpRequest.post(UPayConstant.QUERY_ORDER)
                .header("Authorization", setSignHeader(terminalSn, getSign(params.toString(), terminalKey)))
                .body(params.toString())
                .execute();
        // 返回数据
        log.info("订单查询接口返回数据: {}", httpResponse.body());
        JSONObject jsonObject = JSONUtil.parseObj(httpResponse.body());
        if (jsonObject.containsKey(UPayConstant.RESULT_CODE_NAME)
                && !UPayConstant.RESULT_SUCCESS_CODE.equals(jsonObject.getStr(UPayConstant.RESULT_CODE_NAME))) {
            log.error("订单查询接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                    jsonObject.getStr(UPayConstant.RESULT_CODE_NAME), jsonObject.getStr(UPayConstant.ERROR_CODE_NAME),
                    jsonObject.getStr(UPayConstant.ERROR_MESSAGE_NAME));
            throw new FebsException(ResultMsgType.ORDER_QUERY_FAIL.getValue());
        } else {
            JSONObject bizResponse = jsonObject.getJSONObject(UPayConstant.BIZ_RESPONSE);
            if (bizResponse.containsKey(UPayConstant.BIZ_CODE_NAME)
                    && !UPayConstant.BIZ_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))
                    && !UPayConstant.NO_ORDER_CODE.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))
                    && !UPayConstant.TRADE_NOT_EXIST.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))
            ) {
                log.error("订单查询接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                        bizResponse.getStr(UPayConstant.BIZ_CODE_NAME), bizResponse.getStr(UPayConstant.ERROR_CODE_NAME),
                        bizResponse.getStr(UPayConstant.ERROR_MESSAGE_NAME));
                throw new FebsException(ResultMsgType.ORDER_QUERY_FAIL.getValue());
            }
        }
        return jsonObject;
    }

    /**
     * 退款订单查询
     *
     * @param clientSn        商户订单号
     * @param refundRequestNo 退款序列号
     * @param terminalSn      终端序列号
     * @param terminalKey     终端密钥
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/4/15 15:48
     */
    public static JSONObject queryRefundOrder(String clientSn, String refundRequestNo, String terminalSn, String terminalKey) {
        // 封装请求参数
        JSONObject params = JSONUtil.createObj()
                // 终端序列号
                .set("terminal_sn", terminalSn)
                // 商户订单号
                .set("client_sn", clientSn)
                // 退款序列号
                .set("refund_request_no", refundRequestNo);
        HttpResponse httpResponse = HttpRequest.post(UPayConstant.QUERY_ORDER)
                .header("Authorization", setSignHeader(terminalSn, getSign(params.toString(), terminalKey)))
                .body(params.toString())
                .execute();
        // 返回数据
        log.info("退款查询接口返回数据: {}", httpResponse.body());
        JSONObject jsonObject = JSONUtil.parseObj(httpResponse.body());
        if (jsonObject.containsKey(UPayConstant.RESULT_CODE_NAME)
                && !UPayConstant.RESULT_SUCCESS_CODE.equals(jsonObject.getStr(UPayConstant.RESULT_CODE_NAME))) {
            log.error("退款查询接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                    jsonObject.getStr(UPayConstant.RESULT_CODE_NAME), jsonObject.getStr(UPayConstant.ERROR_CODE_NAME),
                    jsonObject.getStr(UPayConstant.ERROR_MESSAGE_NAME));
            throw new FebsException(ResultMsgType.REFUND_QUERY_FAIL.getValue());
        } else {
            JSONObject bizResponse = jsonObject.getJSONObject(UPayConstant.BIZ_RESPONSE);
            if (bizResponse.containsKey(UPayConstant.BIZ_CODE_NAME)
                    && !UPayConstant.BIZ_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))) {
                log.error("退款查询接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                        bizResponse.getStr(UPayConstant.BIZ_CODE_NAME), bizResponse.getStr(UPayConstant.ERROR_CODE_NAME),
                        bizResponse.getStr(UPayConstant.ERROR_MESSAGE_NAME));
                throw new FebsException(ResultMsgType.REFUND_QUERY_FAIL.getValue());
            }
        }
        return jsonObject;
    }

    /**
     * 退款
     * TODO 2023/4/3 需要验证返回结果
     *
     * @param refund      收钱吧退款类
     * @param terminalSn  终端序列号
     * @param terminalKey 终端密钥
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/3/21 17:50
     */
    public static JSONObject refund(URefund refund, String terminalSn, String terminalKey) {
        // 封装请求参数
        JSONObject params = JSONUtil.createObj()
                // 终端序列号
                .set("terminal_sn", terminalSn)
                // 商户订单号
                .set("client_sn", refund.getClientSn())
                // 退款金额
                .set("refund_amount", String.valueOf(NumberUtil.mul(refund.getRefundAmount(), new BigDecimal("100")).intValue()))
                // 退款序列号
                .set("refund_request_no", refund.getRefundRequestNo())
                // 执行本次退款的操作员
                .set("operator", refund.getOperator());
        log.info(params.toString());
        HttpResponse httpResponse = HttpRequest.post(UPayConstant.REFUND_ORDER)
                .header("Authorization", setSignHeader(terminalSn, getSign(params.toString(), terminalKey)))
                .body(params.toString())
                .execute();
        // 返回数据
        log.info("订单退款接口返回数据: {}", httpResponse.body());
        JSONObject jsonObject = JSONUtil.parseObj(httpResponse.body());
        if (jsonObject.containsKey(UPayConstant.RESULT_CODE_NAME)
                && !UPayConstant.RESULT_SUCCESS_CODE.equals(jsonObject.getStr(UPayConstant.RESULT_CODE_NAME))) {
            log.error("订单退款接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                    jsonObject.getStr(UPayConstant.RESULT_CODE_NAME), jsonObject.getStr(UPayConstant.ERROR_CODE_NAME),
                    jsonObject.getStr(UPayConstant.ERROR_MESSAGE_NAME));
            throw new FebsException(ResultMsgType.ORDER_REFUND_FAIL.getValue());
        } else {
            JSONObject bizResponse = jsonObject.getJSONObject(UPayConstant.BIZ_RESPONSE);
            if (bizResponse.containsKey(UPayConstant.BIZ_CODE_NAME)
                    && !UPayConstant.REFUND_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))
                    && !UPayConstant.NO_ORDER_CODE.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))
                    && !UPayConstant.TRADE_NOT_EXIST.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))) {
                log.error("订单退款接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                        bizResponse.getStr(UPayConstant.BIZ_CODE_NAME), bizResponse.getStr(UPayConstant.ERROR_CODE_NAME),
                        bizResponse.getStr(UPayConstant.ERROR_MESSAGE_NAME));
                throw new FebsException(ResultMsgType.ORDER_REFUND_FAIL.getValue());
            }
        }
        return jsonObject;
    }

    /**
     * 撤单
     *
     * @param clientSn    商户订单号
     * @param terminalSn  终端序列号
     * @param terminalKey 终端密钥
     * @return cn.hutool.json.JSONObject
     * @author ZYJ
     * @date 2023/3/21 17:52
     */
    public static JSONObject closeOrder(String clientSn, String terminalSn, String terminalKey) {
        // 封装请求参数
        JSONObject params = JSONUtil.createObj()
                // 终端序列号
                .set("terminal_sn", terminalSn)
                // 商户订单号
                .set("client_sn", clientSn);

        HttpResponse httpResponse = HttpRequest.post(UPayConstant.CLOSE_ORDER)
                .header("Authorization", setSignHeader(terminalSn, getSign(params.toString(), terminalKey)))
                .body(params.toString())
                .execute();
        // 返回数据
        log.info("撤单接口返回数据: {}", httpResponse.body());
        JSONObject jsonObject = JSONUtil.parseObj(httpResponse.body());
        if (jsonObject.containsKey(UPayConstant.RESULT_CODE_NAME)
                && !UPayConstant.RESULT_SUCCESS_CODE.equals(jsonObject.getStr(UPayConstant.RESULT_CODE_NAME))) {
            log.error("撤单接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                    jsonObject.getStr(UPayConstant.RESULT_CODE_NAME), jsonObject.getStr(UPayConstant.ERROR_CODE_NAME),
                    jsonObject.getStr(UPayConstant.ERROR_MESSAGE_NAME));
            throw new FebsException(ResultMsgType.ORDER_CLOSE_FAIL.getValue());
        } else {
            JSONObject bizResponse = jsonObject.getJSONObject(UPayConstant.BIZ_RESPONSE);
            if (bizResponse.containsKey(UPayConstant.BIZ_CODE_NAME)
                    && !UPayConstant.CANCEL_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))
                    && !UPayConstant.CANCEL_ABORT_SUCCESS_CODE.equals(bizResponse.getStr(UPayConstant.BIZ_CODE_NAME))
                    && !UPayConstant.CANCEL_ORDER_NOOP_CODE.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))
                    && !UPayConstant.NO_ORDER_CODE.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))
                    && !UPayConstant.TRADE_NOT_EXIST.equals(bizResponse.getStr(UPayConstant.ERROR_CODE_NAME))) {
                log.error("撤单接口失败, param : {}, resultCode: {}, errorCode: {}, msg: {}", params,
                        bizResponse.getStr(UPayConstant.BIZ_CODE_NAME), bizResponse.getStr(UPayConstant.ERROR_CODE_NAME),
                        bizResponse.getStr(UPayConstant.ERROR_MESSAGE_NAME));
                throw new FebsException(ResultMsgType.ORDER_CLOSE_FAIL.getValue());
            }
        }
        return jsonObject;
    }

    /**
     * 跳转支付接口签名
     * 排序: 将筛选的参数按照第一个字符的键值ASCII码递增排序（字母升序排序），如果遇到相同字符则按照第二个字符的键值ASCII码递增排序，以此类推。
     * 拼接: 组合成“参数=参数值”的格式，并且把这些参数用&字符连接起来，此时生成的字符串为待签名字符串；将key参数拼接在字符串后面，形式为：“&key=key值”；调用MD5算法生成sign，将sign转换成大写。
     *
     * @param paramStr 请求参数字符串
     * @param key      密钥
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/3/21 14:59
     */
    private static String getPayUrlSign(String paramStr, String key) {
        String signStr = paramStr + "&key=" + key;
        log.info(signStr);
        return DigestUtil.md5Hex(signStr.getBytes()).toUpperCase();
    }

    /**
     * 获取签名
     * MD5( CONCAT( body + key ) )
     *
     * @param params 请求参数
     * @param key    密钥. vendor_Key(激活接口)或terminal_key(其它接口)
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/3/21 11:19
     */
    private static String getSign(String params, String key) {
        String md5Params = params + key;
        return DigestUtil.md5Hex(md5Params.getBytes());
    }

    /**
     * 封装请求头
     *
     * @param sn   签名序列号. vendor_sn(激活接口)或terminal_sn(其它接口)
     * @param sign 签名值. 签名算法: sign = MD5( CONCAT( body + key ) )
     * @return java.lang.String
     * @author ZYJ
     * @date 2023/3/20 17:07
     */
    private static String setSignHeader(String sn, String sign) {
        return sn + " " + sign;
    }

    /**
     * 收钱吧回调验签
     *
     * @param data      回调值
     * @param sign      签名
     * @param publicKey 公钥
     * @return boolean
     * @author ZYJ
     * @date 2023/3/31 14:52
     */
    public static boolean validateSign(String data, String sign, String publicKey) {
        try {
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            PublicKey localPublicKey = getPublicKeyFromX509(KEY_ALGORITHM, publicKey);
            signature.initVerify(localPublicKey);
            signature.update(data.getBytes());
            byte[] bytesSign = Base64.decode(sign);
            return signature.verify(bytesSign);
        } catch (Exception e) {
            log.error("验签失败", e);
            return false;
        }
    }

    private static PublicKey getPublicKeyFromX509(String algorithm, String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decode(publicKey)));
    }


    //    public static void main(String[] args) {
//        // 激活
//        UPayConfig uPayConfig = new UPayConfig();
//        uPayConfig.setVendorSn("91801724");
//        uPayConfig.setVendorKey("80ecee91936004490496304e29b1fe5c");
//        uPayConfig.setWapAppId("2023032000005361");
//        uPayConfig.setCode("18531985");
//        JSONObject jsonObject = active(uPayConfig, "zbz-consume-pay-1");
//        log.info("返回数据: {}", jsonObject);
//    }
//    public static void main(String[] args) {
//        // 下单
//        String url = "http://zbztest.yd-data.com";
//        UPayOrder order = UPayOrder.builder()
//                .clientSn("3E32124asda211213")
//                .totalAmount(new BigDecimal("0.01"))
//                .operator("zbz")
//                .notifyUrl(url)
//                .returnUrl(url)
//                .build();
//        String result = placeOrder(order, "100053610028601010", "50f76a2c11121f22bd4d62e8350ec0f6");
//        log.info(result);
//    }
//    public static void main(String[] args) {
//        // 订单查询
//        queryOrder("1642812693868056576", "11111", "100053610028601010", "50f76a2c11121f22bd4d62e8350ec0f6");
//    }
//    public static void main(String[] args) {
//        // 订单退款
//        refund(URefund.builder()
//                        .clientSn("1642812693868056576")
//                        .refundRequestNo("11111")
//                        .operator("zbz")
//                        .refundAmount(new BigDecimal("0.01"))
//                        .build(),
//                "100053610028601010", "50f76a2c11121f22bd4d62e8350ec0f6");
//    }
//    public static void main(String[] args) {
//        // 撤单
//        closeOrder("3E32124asda211213", "100053610028601010", "50f76a2c11121f22bd4d62e8350ec0f6");
//    }
}
