package com.ruoyi.common.security.utils;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.enums.SignResultCodeEnum;
import com.ruoyi.common.core.enums.SignatureRes;
import com.ruoyi.common.core.utils.ServletUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import static com.ruoyi.common.core.enums.SignatureRes.error;
import static com.ruoyi.common.core.enums.SignatureRes.success;

/**
 * @Description: 接口验证签名工具类
 * @author: gx
 */
public class SignatureUtils {
    /**
     * 签名
     */
    private static final String SIGNATURE = "signature";
    /**
     * 时间戳
     */
    private static final String TIMESTAMP = "timestamp";
    /**
     * 随机字符串
     */
    private static final String NONCE_STR = "noncestr";
    /**
     * 客户端id
     */
    public static final String CLIENT_ID = "clientid";

    //header: {clientid=XX, noncestr=e697ea7d-27c3-42a5-a421-2aa3d7d58efc, timestamp=1590375647, signature=CC78E603495F5AB72727024F4EFF1168}

    /**
     * 验证签名算法
     *
     * @param clientSecret    客户端密钥
     * @return 状态码
     */
    public static SignatureRes checkInterfaceSignature(String clientSecret) {
        //第三方传入的签名
        String signature = ServletUtils.getHeader(SIGNATURE);
        String timestamp = ServletUtils.getHeader(TIMESTAMP);
        String nonceStr = ServletUtils.getHeader(NONCE_STR);
        String clientId = ServletUtils.getHeader(CLIENT_ID);
        Map<String,String> map = new HashMap<>(4);
        map.put(CLIENT_ID, clientId);
        map.put(NONCE_STR, nonceStr);
        map.put(TIMESTAMP, timestamp);

        // 2.验证是否缺少验签所需参数
        //缺少签名参数
        if (StringUtils.isEmpty(signature)) {
            return error(SignResultCodeEnum.MISSING_SIGNATURE);
        }
        //缺少随机时间戳参数
        if (StringUtils.isEmpty(timestamp)) {
            return error(SignResultCodeEnum.MISSING_TIMESTAMP);
        }
        //时间戳是否过期
        Date dateIn = DateUtil.parse(timestamp);
        Date now = DateUtil.date();
        if(DateUtil.between(dateIn, now, DateUnit.SECOND) > 60) {
            return error(SignResultCodeEnum.TIMESTAMP_OUT);
        }
        //缺少随机字符串参数
        if (StringUtils.isEmpty(nonceStr)) {
            return error(SignResultCodeEnum.MISSING_NONCESTR);
        }
        //缺客户端id参数
        if (StringUtils.isEmpty(clientId)) {
            return error(SignResultCodeEnum.MISSING_CLIENT_ID);
        }

        String[] signatureParamsKeys = new String[]{CLIENT_ID, NONCE_STR, TIMESTAMP };

        // 3.对参数进行排序
//        sort(signatureParamsKeys);

        //组装参与签名生成的字符串(使用 URL 键值对的格式（即key1=value1&key2=value2…）拼接成字符串)
        StringBuilder string = new StringBuilder();
        for (int i = 0; i < signatureParamsKeys.length; i++) {
            //拼接随机字符串、时间戳参数
            string.append(signatureParamsKeys[i]).append("=").append(map.get(signatureParamsKeys[i]));

            if (i != signatureParamsKeys.length - 1) {
                string.append("&");
            }
        }

        // 4.生成加密签名
        SM4 sm4 = SmUtil.sm4(clientSecret.getBytes());
        //加密为base64
        String mySignature = getSHA256Str(sm4.encryptBase64(string.toString()));
        // 5.校验签名是否一致
        if (StringUtils.isNotBlank(mySignature) && mySignature.equals(signature)) {
            StringBuilder resSb = new StringBuilder();
            timestamp = DateUtil.date().toString();
            map.put(TIMESTAMP, timestamp);
            for (int i = 0; i < signatureParamsKeys.length; i++) {
                //拼接随机字符串、时间戳参数
                resSb.append(signatureParamsKeys[i]).append("=").append(map.get(signatureParamsKeys[i]));

                if (i != signatureParamsKeys.length - 1) {
                    resSb.append("&");
                }
            }
            String resSignature = getSHA256Str(sm4.encryptBase64(resSb.toString()));

            return success(resSignature, timestamp, SignResultCodeEnum.SUCCESS);
        }
        return error(SignResultCodeEnum.SIGNATURE_MISMATCH);
    }


    /**
     * 字典排序算法
     *
     * @param strArr 待排序数组
     */
    public static void sort(String[] strArr) {
        for (int i = 0; i < strArr.length - 1; i++) {
            for (int j = i + 1; j < strArr.length; j++) {
                if (strArr[j].compareTo(strArr[i]) < 0) {
                    String temp = strArr[i];
                    strArr[i] = strArr[j];
                    strArr[j] = temp;
                }
            }
        }
    }

    /**
     * JSONObject按照参数名ASCII码从小到大排序（字典序）。
     * <p>使用URL键值对的格式（即key1=value1&key2=value2…）拼接成字符串stringA。 特别注意以下重要规则：
     * ◆ 参数名ASCII码从小到大排序（字典序）；
     * ◆ 如果参数的值为空不参与签名； ◆ 参数名区分大小写</p>
     * @param json JSONObject
     * @return StringBuilder
     */
    public static StringBuilder getJsonObjSign(JSONObject json) {
        Set<String> set = json.keySet();
        String[] arr = set.toArray(new String[0]);
        sort(arr);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            if(ObjectUtils.isEmpty(json.get(arr[i]))) {
                continue;
            }
            //拼接参数
            sb.append(arr[i]).append("=").append(json.get(arr[i]));
            if (i != arr.length - 1) {
                sb.append("&");
            }
        }
        return sb;
    }

    /**
     * sha256
     * @param str 原始字符串
     * @return sha256加密转16进制
     */
    public static String getSHA256Str(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) {
            e.printStackTrace();
        }
        return encodeStr;
    }

    /**
     * 将byte转为16进制
     * @param bytes 报文数组
     * @return String
     */
    private static String byte2Hex(byte[] bytes){
        StringBuilder sb = new StringBuilder();
        String temp;
        for (byte aByte : bytes) {
            temp = Integer.toHexString(aByte & 0xFF);
            if (temp.length() == 1) {
                //1得到一位的进行补0操作
                sb.append("0");
            }
            sb.append(temp);
        }
        return sb.toString();
    }

    /**
     * MD5加密
     *
     * @param plainText 待加密字符串
     * @return
     */
    public static String MD5(String plainText) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte[] b = md.digest();
            int i;
            StringBuilder buf = new StringBuilder();
            for (byte b1 : b) {
                i = b1;
                if (i < 0) {
                    i += 256;
                }
                if (i < 16) {
                    buf.append("0");
                }
                buf.append(Integer.toHexString(i));
            }
            // 32位加密
            return buf.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "";
        }
    }

    public static void setSignHeader(String resSignature, String timestamp) {
        Objects.requireNonNull(ServletUtils.getResponse()).setHeader(SIGNATURE, resSignature);
        Objects.requireNonNull(ServletUtils.getResponse()).setHeader(TIMESTAMP, timestamp);
    }
}