package net.northking.cctp.element.basic.keywords;

import cn.hutool.core.convert.Convert;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mifmif.common.regex.Generex;
import net.northking.cctp.element.basic.enums.DecipherTypeEnum;
import net.northking.cctp.element.basic.enums.EncipherTypeEnum;
import net.northking.cctp.element.basic.enums.ParamsTypeEnum;
import net.northking.cctp.element.basic.enums.TypeConversionEnum;
import net.northking.cctp.element.basic.utils.DigestUtils;
import net.northking.cctp.element.basic.utils.ContextUtil;
import net.northking.cctp.element.core.APIFactory;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.annotation.*;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.type.DataType;
import net.northking.cctp.element.core.type.InputType;
import net.northking.cctp.element.core.type.ParamScope;
import net.northking.cctp.element.log.Level;
import net.northking.cctp.element.log.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;

/**
 * Basic
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
@Keywords
public class Common {

    private static final Pattern PATTERN_UNICODE = Pattern.compile("\\\\u[a-f0-9A-F]{1,4}");
    protected static Logger logger = APIFactory.getLogger();

    private BigInteger sequence;

    /**
     * <p>类型转换。</p>
     * 支持对象转换成字符串、数字转换为2进制、数字转换为8进制、数字转换为16进制、2进制转换为数字、8进制转换为数字、16进制转换为数字。
     * <br/>
     * Example:
     * <pre>
     * typeConversion("对象转换成字符串", "你好")    = 你好
     * typeConversion("字符串转换为16进制", "0")     = 30
     * typeConversion("数字转换为2进制", "8")        = 1000
     * typeConversion("数字转换为8进制", "10")       = 12
     * typeConversion("数字转换为16进制", "15")      = F
     * typeConversion("2进制转换为数字", "1000")     = 8
     * typeConversion("8进制转换为数字", "12")       = 10
     * typeConversion("16进制转换为数字", "F")       = 15
     * </pre>
     *
     * @param type    转换类型
     * @param value   数据
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 组件执行结果
     */
    @Keyword(alias = "类型转换")
    @Return(name = "result", type = DataType.STRING, comment = "结果")
    public static String typeConversion(
            @Argument(name = "type", type = DataType.ENUM, comment = "转换类型", inputType = InputType.select, enumValues = TypeConversionEnum.class) String type,
            @Argument(name = "value", type = DataType.OBJECT, comment = "数据对象") Object value,
            @Argument(name = "result", type = DataType.STRING, comment = "结果", scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        TypeConversionEnum typeConversionEnum = TypeConversionEnum.from(type);
        String data;
        switch (typeConversionEnum) {
            case object2Str: {
                data = convertToString(value);
                break;
            }
            case num2Binary: {
                data = convertToBinary(value.toString(), 10);
                break;
            }
            case num2Octal: {
                data = convertToOctal(value.toString(), 10);
                break;
            }
            case num2Hex: {
                data = convertToHex(value.toString(), 10);
                break;
            }
            case binary2Num: {
                data = String.valueOf(convertToInteger(value.toString(), 2));
                break;
            }
            case octal2Num: {
                data = String.valueOf(convertToInteger(value.toString(), 8));
                break;
            }
            case hex2Num: {
                data = String.valueOf(convertToInteger(value.toString(), 16));
                break;
            }
            case str2Hex: {
                data = Convert.toHex(value.toString(), Charset.forName("UTF-8"));
                break;
            }
            default:
                throw new IllegalArgumentException("未知的转换类型:" + value);
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>根据正则表达式，生成随机数据。</p>
     * <i>注意：</i>生成中文使用unicode。
     * Example:
     * <pre>
     * random("135[0-9]{8}")    = 13501349688
     * </pre>
     *
     * @param regex   正则表达式
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 组件执行结果
     */
    @Keyword(alias = "生成随机数据")
    @Return(name = "result", type = DataType.STRING, comment = "字符串")
    public static String random(
            @Argument(name = "regex", type = DataType.OBJECT, comment = "正则表达式") String regex,
            @Argument(name = "result", type = DataType.STRING, comment = "字符串", scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        try {
            Pattern.compile(regex);
            //特殊符号前反斜杠校验 @#
            char[] chars = regex.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                if (chars[i] == '@' || chars[i] == '#') {
                    if (i == 0 || chars[i - 1] != '\\') {
                        throw new IllegalArgumentException(String.format("正则表达式有误:%s", regex));
                    }
                }
            }
        } catch (Exception e) {
            throw new IllegalArgumentException(String.format("正则表达式有误:%s", regex), e);
        }
        regex = mixStrToString(regex);
        Generex generex = new Generex(regex);
        String data = generex.random();
        // 因为匹配出来的结果还存在定位符^和$，故只能先写死替换先，暂未找到合适的方案
        if (data.startsWith("^")){
            data = data.replace("^", "");
        }
        if (data.endsWith("$")){
            data = data.replace("$", "");
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>加密文本内容。</p>
     * Example:
     * <pre>
     * encipher("Base64加密", "0123456789abcdef", "UTF-8")    = MDEyMzQ1Njc4OWFiY2RlZg==
     * encipher("MD5加密", "0123456789abcdef", "UTF-8")       = 4032af8d61035123906e58e067140cc5
     * encipher("CRC32加密", "0123456789abcdef", "UTF-8")     = 68c4f033
     * encipher("SHA加密", "0123456789abcdef", "UTF-8")       = fe5567e8d769550852182cdf69d74bb16dff8e29
     * encipher("SHA-224加密", "0123456789abcdef", "UTF-8")   = 7330215f6741fd2bacbd3658681a70f65e2e90a02887989018974ce8
     * encipher("国密SM4加密", "0123456789abcdef", "UTF-8", "31362a38616263643132333435363738")    =   af9209ddca33521473088ca987bf5b0a03333dad5640ca276fc63f16999f7985  // 128bit秘钥
     * </pre>
     *
     * @param type    加密类型
     * @param text    明文字符串
     * @param charset 字符集
     * @param hexKey  SM4加密时传入的16进制秘钥字符串
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 组件执行结果
     */
    @Keyword(alias = "加密")
    @Return(name = "result", type = DataType.STRING, comment = "字符串")
    public static String encipher(
            @Argument(name = "type", comment = "加密类型", type = DataType.ENUM, enumValues = EncipherTypeEnum.class, inputType = InputType.select) String type,
            @Argument(name = "text", comment = "明文字符串", type = DataType.STRING) String text,
            @Argument(name = "charset", comment = "字符集", type = DataType.STRING, defaultValue = "UTF-8") String charset,
            @Argument(name = "hexKey", type = DataType.STRING, comment = "SM4加密时的16进制秘钥", required = false) String hexKey,
            @Argument(name = "result", comment = "字符串", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        EncipherTypeEnum typeEnum = EncipherTypeEnum.from(type);
        String data;
        switch (typeEnum) {
            case base64: {
                data = base64Encode(text, charset);
                break;
            }
            case md5: {
                data = md5DigestAsHex(text, charset);
                break;
            }
            case crc32: {
                data = crc32AsHex(text, charset);
                break;
            }
            case sha: {
                data = shaDigestAsHex("SHA", text, charset);
                break;
            }
            case sha224: {
                data = shaDigestAsHex("SHA-224", text, charset);
                break;
            }
            case sha256: {
                data = shaDigestAsHex("SHA-256", text, charset);
                break;
            }
            case sha384: {
                data = shaDigestAsHex("SHA-384", text, charset);
                break;
            }
            case sha512: {
                data = shaDigestAsHex("SHA-512", text, charset);
                break;
            }
            case sm4: {
                data = sm4Encrypt(hexKey, text, charset, "HEX");
                break;
            }
            default:
                throw new IllegalArgumentException("未知的加密类型:" + type);
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>解密文本内容。</p>
     * Example:
     * <pre>
     * decipher("Base64解密", "MDEyMzQ1Njc4OWFiY2RlZg==", "UTF-8")    = 0123456789abcdef
     * decipher("国密SM4解密", "af9209ddca33521473088ca987bf5b0a03333dad5640ca276fc63f16999f7985", "UTF-8", "31362a38616263643132333435363738")    =   0123456789abcdef  // 128bit秘钥
     * </pre>
     *
     * @param type    解密类型
     * @param text    密文字符串
     * @param charset 字符集
     * @param hexKey  SM4解密时传入的16进制秘钥字符串
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 组件执行结果
     */
    @Keyword(alias = "解密")
    @Return(name = "result", type = DataType.STRING, comment = "字符串")
    public static String decipher(
            @Argument(name = "type", comment = "解密类型", type = DataType.ENUM, enumValues = DecipherTypeEnum.class, inputType = InputType.select) String type,
            @Argument(name = "text", comment = "密文字符串", type = DataType.STRING) String text,
            @Argument(name = "charset", comment = "字符集", type = DataType.STRING, defaultValue = "UTF-8") String charset,
            @Argument(name = "hexKey", type = DataType.STRING, comment = "SM4解密时的16进制秘钥", required = false) String hexKey,
            @Argument(name = "result", comment = "字符串", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        DecipherTypeEnum typeEnum = DecipherTypeEnum.from(type);
        String data;
        switch (typeEnum) {
            case base64: {
                data = base64Decode(text, charset);
                break;
            }
            case sm4: {
                data = sm4Decrypt(hexKey, text, charset);
                break;
            }
            default:
                throw new IllegalArgumentException("未知的解密类型:" + type);
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>获取字符串、数组长度及集合类型元素个数</p>
     *
     * @param object  字符串(String)、数组(Array)或集合(List/Map)对象
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 字符串长度或元素个数
     */
    @Keyword(alias = "获取长度")
    @Return(name = "result", comment = "字符串长度或元素个数", type = DataType.LONG)
    public static long getLength(
            @Argument(name = "object", comment = "数据对象", type = DataType.OBJECT) Object object,
            @Argument(name = "type", comment = "数据类型", type = DataType.ENUM,enumValues = ParamsTypeEnum.class,inputType = InputType.select) String type,
            @Argument(name = "result", comment = "结果", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        int data;
        ParamsTypeEnum typeEnum = ParamsTypeEnum.from(type);
        switch (typeEnum){
            case string:{
                data = object.toString().length();
                break;
            }
            case array:{
                data = JSONArray.parseArray(object.toString()).size();
                break;
            }
            case map:{
                data = JSONObject.parseObject(object.toString()).size();
                break;
            }
            default:
                throw new IllegalArgumentException("获取长度失败, 参数类型不支持");
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>记录日志信息</p>
     * 日志级别: <i>INFO</i>、<i>DEBUG</i>、<i>WARN</i>、<i>ERROR</i>
     *
     * @param message 日志信息
     * @param level   日志级别
     * @param context 执行上下文
     */
    //@Keyword(alias = "日志输出")
    public void log(
            @Argument(name = "message", comment = "日志信息", type = DataType.OBJECT) Object message,
            @Argument(name = "level", comment = "日志级别", type = DataType.STRING, defaultValue = "INFO") String level,
            IExecuteContext context) {
        Level logLevel = Level.formatLevel(level);
        logger.log(logLevel, "{}", message);
    }

    /**
     * <p>接受一个表达式，运算后返回结果。</p>
     * Example:
     * <pre>
     * var a = assign( ${a} + "abc" + ${b}  )
     * var b = assign("你好")
     * </pre>
     *
     * @param object  表达式
     * @param result  组件返回值
     * @param context 执行上下文
     * @return 返回结果
     */
    @Keyword(alias = "赋值")
    @Return(name = "result", comment = "结果", type = DataType.OBJECT)
    public static Object assign(
            @Argument(name = "object", comment = "表达式", type = DataType.OBJECT, expression = true) Object object,
            @Argument(name = "result", comment = "结果", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        Object data;
        if (object instanceof String) {
            String expression = (String) object;
            data = context.evalExpression(expression);
        } else {
            data = object;
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>等待一段时间, 接着执行后续操作。</p>
     *
     * @param seconds 等待时间(秒)
     * @throws Exception
     */
    @Keyword(alias = "等待")
    public void sleep(
            @Argument(name = "seconds", comment = "等待时间(秒)", type = DataType.LONG) long seconds) throws Exception {
        if (seconds < 1) {
            throw new ExecuteException("等待时间必须为正整数");
        }
        logger.info("执行等待组件, 等待{}秒.", seconds);
        TimeUnit.SECONDS.sleep(seconds);
        logger.info("等待{}秒结束.", seconds);
    }

    /**
     * <p>字符串文本是否与正则式匹配</p>
     * Example:
     * <pre>
     * regexMatch("13834569087@163.com", "138[0-9]{8}@[0-9a-zA-Z]+.com") = true
     * </pre>
     *
     * @param text    字符串文本
     * @param regex   正则表达式
     * @param result  组件返回值
     * @param context 执行上下文
     * @return true/false
     */
    @Keyword(alias = "文本是否与正则式匹配")
    @Return(name = "result", comment = "是否匹配", type = DataType.BOOLEAN)
    public static boolean regexMatch(
            @Argument(name = "text", comment = "字符串文本", type = DataType.STRING) String text,
            @Argument(name = "regex", comment = "正则表达式", type = DataType.STRING) String regex,
            @Argument(name = "result", comment = "是否匹配", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        boolean data = Pattern.matches(regex, text);
        ContextUtil.setResult(context, result, data);
        return data;
    }

    /**
     * <p>断言表达式是否成立</p>
     * Example:
     * <pre>
     * var var1 = 1,var2 = 4;
     * var str1 = "1";
     * assertExpression( ${var1} == 1 && ${var2} > 3 ) = true
     * assertExpression( ${str1} + "a" == "1a" ) = true
     * </pre>
     *
     * @param expression 表达式: ${var1} == 1 && ${var2} > 3
     */
    @Keyword(alias = "断言满足表达式")
    public boolean assertExpression(
            @Argument(name = "expression", comment = "表达式", type = DataType.STRING, expression = true) String expression,
            IExecuteContext context) {
        boolean result = ContextUtil.evalBooleanExpression(context, expression);
        if (!result) {
            logger.info("断言失败，实际结果与表达式" + expression + "预期不符");
        }
        return result;
    }

    /**
     * <p>在指定范围内生成序列号，序列到最大值时会重新回到开始序号。</p>
     * Example:
     * <pre>
     * sequenceNo( 0, 99999, true) => 00000、00001、00002...99999
     * sequenceNo( 0, 99999, false) => 0、1、2...99999
     * </pre>
     *
     * @param start   开始序号
     * @param end     结束序号
     * @param leftPad 是否左补0
     */
    @Keyword(alias = "生成序列号")
    @Return(name = "result", comment = "序列号", type = DataType.STRING)
    public String sequenceNo(
            @Argument(name = "start", comment = "开始序号", type = DataType.STRING) String start,
            @Argument(name = "end", comment = "结束序号", type = DataType.STRING) String end,
            @Argument(name = "leftPad", comment = "是否左补0", type = DataType.BOOLEAN, required = false, defaultValue = "true") boolean leftPad,
            @Argument(name = "result", comment = "序列号", type = DataType.STRING, scope = ParamScope.OUTPUT) String result,
            IExecuteContext context) {
        if (!NumberUtils.isParsable(start) || StringUtils.containsAny(start, ".", "-")) {
            throw new IllegalArgumentException("开始序号必须为非负整数，请参考示例");
        }
        if (!NumberUtils.isParsable(end) || StringUtils.containsAny(end, ".", "-")) {
            throw new IllegalArgumentException("结束序号必须为非负整数，请参考示例");
        }
        if (new BigInteger(start).compareTo(new BigInteger(end)) > 0) {
            throw new IllegalArgumentException("开始序号不能大于结束序号，请参考示例");
        }
        if (sequence == null) {
            sequence = new BigInteger(start);
        } else {
            BigInteger max = new BigInteger(end);
            BigInteger newSequence = sequence.add(new BigInteger("1"));
            sequence = newSequence.compareTo(max) > 0 ? new BigInteger(start) : newSequence;
        }
        String data = sequence.toString();
        if (leftPad) {
            data = StringUtils.leftPad(data, end.length(), "0");
        }
        ContextUtil.setResult(context, result, data);
        return data;
    }


    // ##########################################################################################################
    // Internal Methods
    // ##########################################################################################################

    /**
     * <p>获取给定对象的toString方法返回值。</p>
     *
     * @param object 数据对象
     * @return 字符串
     */
    private static String convertToString(Object object) {
        return object == null ? null : object.toString();
    }

    /**
     * <p>将给定项转换为二进制字符串。</p>
     * Example:
     * <pre>
     * convertToBinary(10, 8)      = 1000
     * convertToBinary(-2, 10)     = -10
     * convertToBinary(1a, 16)     = 11010
     * </pre>
     *
     * @param item  8、10、16进制数据
     * @param radix 8、10、16进制
     * @return 二进制字符串
     */
    private static String convertToBinary(String item, int radix) {
        if (radix == 8 || radix == 10 || radix == 16) {
            BigInteger bigInteger = new BigInteger(item, radix);
            return bigInteger.toString(2);
        }
        throw new IllegalArgumentException("仅支持8、10、16进制转2进制");
    }

    /**
     * <p>将给定项转换为八进制字符串。</p>
     * Example:
     * <pre>
     * convertToOctal(1000, 2)    = 10
     * convertToOctal(10, 10)     = 12
     * convertToOctal(F, 16)      = 17
     * </pre>
     *
     * @param item  2、10、16进制数据
     * @param radix 2、10、16进制
     * @return 八进制字符串
     */
    private static String convertToOctal(String item, int radix) {
        if (radix == 2 || radix == 10 || radix == 16) {
            BigInteger bigInteger = new BigInteger(item, radix);
            return bigInteger.toString(8);
        }
        throw new IllegalArgumentException("仅支持2、10、16进制转8进制");
    }

    /**
     * <p>将给定项转换为十六进制字符串。</p>
     * Example:
     * <pre>
     * convertToHex(10, 2)     = 2
     * convertToHex(12, 8)     = A
     * convertToHex(15, 10)    = F
     * </pre>
     *
     * @param item  2、8、10进制数据
     * @param radix 2、8、10进制
     * @return 十六进制字符串
     */
    private static String convertToHex(String item, int radix) {
        if (radix == 2 || radix == 8 || radix == 10) {
            BigInteger bigInteger = new BigInteger(item, radix);
            return bigInteger.toString(16).toUpperCase();
        }
        throw new IllegalArgumentException("仅支持2、8、10进制转16进制");
    }

    /**
     * <p>将给定项转换为整数。</p>
     * Example:
     * <pre>
     * convertToInteger(10, 2)     = 2
     * convertToInteger(12, 8)     = 10
     * convertToInteger(18, 10)    = 18
     * convertToInteger(F, 16)     = 15
     * </pre>
     *
     * @param item  2、8、10、16进制数据
     * @param radix 2、8、10、16进制
     * @return int类型数
     */
    private static String convertToInteger(String item, int radix) {
        if (radix == 2 || radix == 8 || radix == 10 || radix == 16) {
            BigInteger bigInteger = new BigInteger(item, radix);
            return bigInteger.toString();
        }
        throw new IllegalArgumentException("仅支持2、8、10、16进制转int类型");
    }

    /**
     * <p>Base64编码。</p>
     * Example:
     * <pre>
     * base64Encode("abcd1234efg你好", "UTF-8")    = YWJjZDEyMzRlZmfkvaDlpb0=
     * </pre>
     *
     * @param text    字符串文本
     * @param charset 字符集
     * @return base64编码字符串
     */
    private static String base64Encode(String text, String charset) {
        Base64.Encoder encoder = Base64.getEncoder();
        String encode = encoder.encodeToString(text.getBytes(Charset.forName(charset)));
        return encode;
    }

    /**
     * <p>CRC32加密。</p>
     * Example:
     * <pre>
     * crc32AsHex("0123456789abcdef", "UTF-8")    = 68c4f033
     * </pre>
     *
     * @param text    字符串文本
     * @param charset 字符集
     * @return crc32加密16进制字符串
     */
    private static String crc32AsHex(String text, String charset) {
        CRC32 crc = new CRC32();
        crc.update(text.getBytes(Charset.forName(charset)));
        return Long.toHexString(crc.getValue());
    }

    /**
     * <p>MD5加密。</p>
     * Example:
     * <pre>
     * md5DigestAsHex("0123456789abcdef", "UTF-8")    = 4032af8d61035123906e58e067140cc5
     * </pre>
     *
     * @param text    字符串文本
     * @param charset 字符集
     * @return md5加密串
     */
    private static String md5DigestAsHex(String text, String charset) {
        return DigestUtils.md5DigestAsHex(text.getBytes(Charset.forName(charset)));
    }

    /**
     * <p>SHA加密，加密结果散列值转为16进制字符串。</p>
     * Example:
     * <pre>
     * shaDigestAsHex("SHA","0123456789abcdef","UTF-8")      = FE5567E8D769550852182CDF69D74BB16DFF8E29
     * shaDigestAsHex("SHA-224","0123456789abcdef","UTF-8")  = 7330215F6741FD2BACBD3658681A70F65E2E90A02887989018974CE8
     * </pre>
     * SHA加密算法：<br>
     * <table border="0">
     * <tr>
     * <td width="100">算法名称</td>
     * <td>描述</td>
     * </tr>
     * <tr>
     * <td><i>SHA</i></td>
     * <td>160比特</td>
     * </tr>
     * <tr>
     * <td><i>SHA-224</i></td>
     * <td>224比特</td>
     * </tr>
     * <tr>
     * <td><i>SHA-256</i></td>
     * <td>256比特</td>
     * </tr>
     * <tr>
     * <td><i>SHA-384</i></td>
     * <td>384比特</td>
     * </tr>
     * <tr>
     * <td><i>SHA-512</i></td>
     * <td>512比特</td>
     * </tr>
     * </table>
     *
     * @param shaAlgorithm sha加密算法
     * @param text         字符串文本
     * @param charset      字符集
     * @return sha加密16进制字符串
     */
    private static String shaDigestAsHex(String shaAlgorithm, String text, String charset) {
        try {
            MessageDigest sha = MessageDigest.getInstance(shaAlgorithm);
            sha.update(text.getBytes(Charset.forName(charset)));
            return Convert.toHex(sha.digest());
        } catch (Exception e) {
            throw new ExecuteException(String.format("%s加密失败", shaAlgorithm), e);
        }
    }

    /**
     * 国密SM4加密，加密结果用Hex 16进制字符串表示
     *
     * @param hexKey      16进制秘钥字符串
     * @param data        需要加密的数据
     * @param charsetName 字符集默认UTF-8
     * @param resultType  结果类型HEX或Base64
     * @return 返回Hex HEX或Base64字符串表示
     */
    private static String sm4Encrypt(
            @Argument(name = "hexKey", type = DataType.STRING, comment = "16进制秘钥字符串") String hexKey,
            @Argument(name = "data", type = DataType.STRING, comment = "需要加密的数据") String data,
            @Argument(name = "charsetName", type = DataType.STRING, comment = "字符集", defaultValue = "UTF-8") String charsetName,
            @Argument(name = "resultType", type = DataType.STRING, comment = "结果类型HEX或Base64", defaultValue = "HEX") String resultType) {
        try {
            SM4 sm4 = SmUtil.sm4(Convert.hexToBytes(hexKey));
            return "HEX".equalsIgnoreCase(resultType) ? sm4.encryptHex(data, charsetName) : sm4.encryptBase64(data, charsetName);
        } catch (Exception e) {
            throw new ExecuteException(e.getMessage());
        }
    }

    /**
     * <p>Base64解码。</p>
     * Example:
     * <pre>
     * base64Decode("YWJjZDEyMzRlZmfkvaDlpb0=", "UTF-8")    = abcd1234efg你好
     * </pre>
     *
     * @param text    base64字符串
     * @param charset 字符集
     * @return base64解码字符串
     */
    private static String base64Decode(
            @Argument(name = "text", comment = "base64字符串", type = DataType.STRING) String text,
            @Argument(name = "charset", comment = "字符集", type = DataType.STRING, defaultValue = "UTF-8") String charset) {
        Base64.Decoder decoder = Base64.getDecoder();
        String decode = new String(decoder.decode(text), Charset.forName(charset));
        return decode;
    }

    /**
     * 国密SM4解密
     *
     * @param hexKey      16进制秘钥字符串
     * @param data        需要解密的数据（HEX或Base64）
     * @param charsetName 字符集
     * @return 返回解密后字符串
     */
    public static String sm4Decrypt(
            @Argument(name = "hexKey", type = DataType.STRING, comment = "16进制秘钥字符串") String hexKey,
            @Argument(name = "data", type = DataType.STRING, comment = "需要解密的数据") String data,
            @Argument(name = "charsetName", type = DataType.STRING, comment = "字符集", defaultValue = "UTF-8") String charsetName) {
        try {
            SM4 sm4 = SmUtil.sm4(Convert.hexToBytes(hexKey));
            return sm4.decryptStr(data, Charset.forName(charsetName));
        } catch (Exception e) {
            throw new ExecuteException(e.getMessage());
        }
    }

    private static String mixStrToString(String mixStr) {
        if (StringUtils.isBlank(mixStr)) {
            return mixStr;
        }
        int start = 0;
        StringBuilder result = new StringBuilder();
        Matcher matcher = PATTERN_UNICODE.matcher(mixStr);
        while (matcher.find()) {
            String oldChar = matcher.group();
            result.append(mixStr, start, matcher.start());
            result.append(unicodeToChar(oldChar));
            start = matcher.start() + oldChar.length();
        }
        result.append(mixStr.substring(start));
        return result.toString();
    }

    private static String unicodeToChar(String unicode) {
        if (StringUtils.isBlank(unicode)) {
            return unicode;
        }
        StringBuilder sb = new StringBuilder();
        String[] hex = unicode.split("\\\\u");
        for (int index = 1; index < hex.length; index++) {
            int data = Integer.parseInt(hex[index], 16);
            sb.append((char) data);
        }
        return sb.toString();
    }

}
