package com.yn.bftl.thirdparty.modules.unionpay.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yn.bftl.common.common.exception.YnceError;
import com.yn.bftl.common.common.util.RouteUtil;
import com.yn.bftl.common.modules.configuration.entity.ThirdPartyPaySet;
import com.yn.bftl.common.modules.order.entity.AfterSale;
import com.yn.bftl.common.modules.order.entity.Order;
import com.yn.bftl.thirdparty.common.exception.YnceErrorException;
import com.yn.bftl.thirdparty.common.repository.PaymentRepository;
import com.yn.bftl.thirdparty.common.util.HttpClientUtil;
import com.yn.bftl.thirdparty.common.repository.RefundRepository;
import com.yn.bftl.thirdparty.modules.business.util.ThirdPartyConfigUtil;
import com.yn.bftl.thirdparty.modules.log.service.LoggerService;
import com.yn.bftl.thirdparty.modules.unionpay.enums.UnionpayAppOrderType;
import com.yn.bftl.thirdparty.modules.unionpay.vo.base.UnionpayResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.qiyuesuo.sdk.v2.utils.CryptUtils.byte2Hex;


/**
 * 银联工具类
 *
 * @author huabiao
 * @create 2022/8/29  21:15
 **/
@Slf4j
@Component
public class UnionpayUtil {

    @Resource
    private ThirdPartyConfigUtil thirdPartyConfigUtil;
    @Resource
    private PaymentRepository paymentRepository;
    @Resource
    private RefundRepository refundRepository;
    @Resource
    private LoggerService loggerService;


    /**
     * 获取第三方配置参数
     *
     * @param companyId 企业ID
     * @return 第三方支付配置
     * @description
     * @author huabiao
     * @create 2022/9/1  15:29
     */
    public ThirdPartyPaySet getThirdPartyPaySet(Long companyId) {
        ThirdPartyPaySet thirdPartyPaySet = thirdPartyConfigUtil.getCompany(companyId);
        if (thirdPartyPaySet == null) {
            throw new YnceErrorException(YnceError.YNCE_218001, "未配置参数");
        }
        if (StringUtils.isBlank(thirdPartyPaySet.getUnionpayDomainName())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayAppId())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayAppKey())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpaySourceCode())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayMid())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayTid())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayWechatSubAppId())
                || StringUtils.isBlank(thirdPartyPaySet.getUnionpayCallbackUrl())
                || StringUtils.isBlank(thirdPartyPaySet.getPosAppId())
                || StringUtils.isBlank(thirdPartyPaySet.getPosRechargeBalanceAcctId())) {
            throw new YnceErrorException(YnceError.YNCE_218001, "未配置银联参数");
        }
        return thirdPartyPaySet;
    }

    /**
     * 银联-生成商户订单号
     *
     * @param clazz：类(Order:银联订单号、AfterSale:银联退款单号)
     * @return 商户订单号
     * @author huabiao
     * @create 2022/9/1  11:34
     */
    public String generateUnionpaySn(Class clazz, Long companyId) {
        String currentDate = new SimpleDateFormat("yyyyMMddmmHHssSSS").format(System.currentTimeMillis());
        String unionpaySn = getThirdPartyPaySet(companyId).getUnionpaySourceCode() + currentDate + getRandomNumber(7);
        if (clazz.getName().equals(Order.class.getName())) {
            if (paymentRepository.existsByUnionpaySn(unionpaySn)) {
                return generateUnionpaySn(Order.class, companyId);
            } else {
                return unionpaySn;
            }
        } else if (clazz.getName().equals(AfterSale.class.getName())) {
            if (refundRepository.existsByUnionpayRefundSn(unionpaySn)) {
                return generateUnionpaySn(AfterSale.class, companyId);
            } else {
                return unionpaySn;
            }
        } else {
            throw new YnceErrorException(YnceError.YNCE_100001);
        }
    }


    /**
     * 获取签名头
     *
     * @param appId,
     * @param appKey
     * @param timestamp 格式:"yyyyMMddHHmmss"
     * @param body      请求体
     * @return authorization 认证报文
     * @throws Exception
     */
    public String getAuthorization(String appId, String appKey, String timestamp, String body) throws Exception {
        String nonce = getNonceStr();
        byte[] data = body.getBytes("utf-8");
        InputStream is = new ByteArrayInputStream(data);
        String testSH = DigestUtils.sha256Hex(is);
        String s1 = appId + timestamp + nonce + testSH;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(appKey.getBytes("utf-8"), "HmacSHA256"));
        byte[] localSignature = mac.doFinal(s1.getBytes("utf-8"));
        String localSignatureStr = Base64.encodeBase64String(localSignature);
        log.debug("Authorization：" + "OPEN-BODY-SIG AppId=" + "\"" + appId + "\"" + ", Timestamp=" + "\"" + timestamp + "\"" + ", Nonce=" + "\"" + nonce + "\"" + ", Signature=" + "\"" + localSignatureStr + "\"");
        return "OPEN-BODY-SIG AppId=" + "\"" + appId + "\"" + ", Timestamp=" + "\"" + timestamp + "\"" + ", Nonce=" + "\"" + nonce + "\"" + ", Signature=" + "\"" + localSignatureStr + "\"";
    }

    /**
     * 生成随机数
     *
     * @param length:随机数长度
     * @return 随机数
     * @author huabiao
     * @create 2022/9/1  15:18
     */
    private static String getRandomNumber(int length) {
        Random rand = new Random();//生成随机数
        String cardNumber = "";
        for (int a = 0; a < length; a++) {
            cardNumber += rand.nextInt(10);
        }
        return cardNumber;
    }

    /**
     * 银联-获取请求路径
     *
     * @param unionpayAppOrderType:银联支付类型
     * @return 请求路径
     * @author huabiao
     * @create 2022/9/6  11:31
     */
    public String getRequestUrl(UnionpayAppOrderType unionpayAppOrderType) {
        switch (unionpayAppOrderType) {
            case WECHAT:
                return RouteUtil.ROUTE_UNIONPAY_WECHAT;
            case ALIPAY:
                return RouteUtil.ROUTE_UNIONPAY_ALIPAY_APPLET;
            case CHINA_UNIONPAY_QUICK_PASS:
                return RouteUtil.ROUTE_UNIONPAY_CLOUD_PAY;
            default:
                return null;
        }
    }

    /**
     * 获取随机字符串
     *
     * @return
     * @description
     * @author huabiao
     * @create 2022/9/6  11:30
     */
    private static String getNonceStr() {
        //随机数数组
        char[] CHARS = {
                '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm',
                'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
                'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
        };
        Random random = new Random();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            buffer.append(CHARS[random.nextInt(CHARS.length)]);
        }
        return buffer.toString();
    }

    /**
     * @param url:请求路径
     * @param request:请求参数
     * @param requestTimestamp:
     * @param requestTimestamp:
     * @return
     * @description
     * @author huabiao
     * @create 2022/9/6  11:22
     */
    public UnionpayResponseVO execute(String url, String request, String requestTimestamp, Long companyId) {
        log.debug("银联聚合支付请求路径：{}，请求参数：{}", url, request);
        UnionpayResponseVO unionpayResponseVo;
        try {
            ThirdPartyPaySet thirdPartyPaySet = getThirdPartyPaySet(companyId);
            // 拼接请求头
            Map<String, String> headerMap = new HashMap<>();
            headerMap.put("authorization", getAuthorization(thirdPartyPaySet.getUnionpayAppId(), thirdPartyPaySet.getUnionpayAppKey(), requestTimestamp, request));
            // 调用请求接口
            JSONObject jsonObject = HttpClientUtil.getInstance().setHeaderMap(headerMap).sendPost(thirdPartyPaySet.getUnionpayDomainName() + url, request);
            unionpayResponseVo = jsonObject.toJavaObject(UnionpayResponseVO.class);
            log.debug("银联聚合支付响应参数：{}", jsonObject.toJSONString());
            if (unionpayResponseVo.getErrCode().equals("SUCCESS")) {
                loggerService.unionpay(jsonObject.toJSONString(), JSON.parseObject(request), UnionpayUtil.class.getName(), url, "unionpay");
                return unionpayResponseVo;
            } else if (unionpayResponseVo.getErrCode().equals("TARGET_FAIL")) {
                // 当多次调用退款时，如果前面已退款成功，后续调用都返回成功（解决本方系统出现异常，而银联方却正常退款的情况）
                if ("FAIL".equals(jsonObject.getString("refundStatus")) && StringUtils.isNotBlank(jsonObject.getString("refundOrderId"))) {
                    loggerService.unionpay(jsonObject.toJSONString(), JSON.parseObject(request), UnionpayUtil.class.getName(), url, "unionpay");
                    return unionpayResponseVo;
                } else {
                    throw new RuntimeException(unionpayResponseVo.getErrMsg());
                }
            } else {
                throw new RuntimeException(unionpayResponseVo.getErrMsg());
            }
        } catch (final Exception e) {
            loggerService.unionpay(e.getMessage(), JSON.parseObject(request), UnionpayUtil.class.getName(), url, "unionpay");
            throw new YnceErrorException(YnceError.YNCE_218001, e.getMessage());
        }
    }


    /**
     * 获取签名Sign
     *
     * @return
     */
    public static String getSign(Map<String, String> params, String unionpayKey) {
        Collection<String> keySet = params.keySet();
        List<String> keyList = new ArrayList<>(keySet);
        //对key键值按字典升序排序(ASCII码从小到大排序)
        Collections.sort(keyList);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < keyList.size(); i++) {
            String key = keyList.get(i);
            stringBuilder.append(key);
            stringBuilder.append("=");
            stringBuilder.append(params.get(key));
            if (i != (keyList.size() - 1)) {
                stringBuilder.append("&");
            }
        }
        stringBuilder.append(unionpayKey);
        return getMessageDigest(stringBuilder.toString());
    }

    private final static String getMessageDigest(String str) {
        MessageDigest messageDigest;
        String encodeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
            encodeStr = byte2Hex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
        return encodeStr;
    }

    /**
     * 验签测试
     *
     * @param args:
     * @return
     * @description
     * @author huabiao
     * @create 2022/9/6  16:49
     */
    public static void main(String[] args) {
        Map<String, String> params = new HashMap<>();
        params.put("walletOption", "SINGLE");
        params.put("billNo", "31940000201700002");
        params.put("billDate", "2017-06-26");
        params.put("walletOption", "SINGLE");
        params.put("requestTimestamp", "2017-06-26 17:28:02");
        params.put("instMid", "QRPAYDEFAULT");
        params.put("msgSrc", "WWW.TEST.COM");
        params.put("totalAmount", "1");
        params.put("goods", "[{\"body\":\"微信二维码测试\",\"price\":\"1\",\"goodsName\":\"微信二维码测试\",\"goodsId\":\"1\",\"quantity\":\"1\",\"goodsCategory\":\"TEST\"}]");
        params.put("msgType", "bills.getQRCode");
        params.put("mid", "898340149000005");
        params.put("tid", "88880001");

        String key = "fcAmtnx7MwismjWNhNKdHC44mNXtnEQeJkRrhKJwyrW2ysRR";
        System.out.println(getSign(params, key));
    }
}
