package com.hzya.frame.seeyon.util;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class SM2Util {

    private final static SM2Engine.Mode DIGEST = SM2Engine.Mode.C1C3C2;

    private static final String PRIVATE_KEY = "privateKey";

    private static final String PUBLIC_KEY = "publicKey";

    private static final String STD_NAME = "sm2p256v1";

    private static final String ALGORITHM = "EC";

    public static final String LF = "\n";

    public static final String CR = "\r";

    public static final String SPACE = " ";

    public static final String EMPTY = "";


    /**
     * 生成密钥
     *
     * @return
     */
    public Map<String, String> generate() {
        Map<String, String> keyMap = new HashMap<>();
        try {
            ECGenParameterSpec sm2Spec = new ECGenParameterSpec(STD_NAME);
            KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM, new BouncyCastleProvider());
            kpg.initialize(sm2Spec);
            KeyPair keyPair = kpg.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            keyMap.put(PRIVATE_KEY, new String(Base64.getEncoder().encode(privateKey.getEncoded()), StandardCharsets.UTF_8));
            keyMap.put(PUBLIC_KEY, new String(Base64.getEncoder().encode(publicKey.getEncoded()), StandardCharsets.UTF_8));
        } catch (Exception e) {
            /*this.log.info("SM2Util生成密钥出现异常", e);*/
        }
        return keyMap;
    }

    /**
     * 加密
     *
     * @param data      数据Str
     * @param publicKey 公钥Str
     * @return 加密之后的数据
     */
    public static String encrypt(String data, String publicKey)  {
        try {
            return new String(Base64.getEncoder().encode(encrypt(data.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(publicKey.getBytes(StandardCharsets.UTF_8)))));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密
     *
     * @param data      数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
        CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams("SM2", publicKey));
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(true, pubKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 解密
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static String decrypt(String data, String privateKey) throws Exception {
        return new String(decrypt(Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)), Base64.getDecoder().decode(privateKey.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
    }

    /**
     * 解密
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
        CipherParameters privateKeyParameters = privateKeyToParams("SM2", privateKey);
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(false, privateKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * SM2密文转换空格等特殊字符
     *
     * @param sm2Content
     * @return
     */
    public static String fixSm2Content(String sm2Content) {
        if (sm2Content == null) {
            return sm2Content;
        }
        return sm2Content.replace(LF, EMPTY).replace(CR, EMPTY).replace(SPACE, "+");
    }

    /**
     * 私钥转换为 {@link ECPrivateKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPrivateKeyParameters privateKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        if (key == null || key.length == 0) {
            throw new NullPointerException("key must be not null !");
        }
        PrivateKey privateKey = generatePrivateKey(algorithm, key);
        return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
    }

    /**
     * 生成私钥
     *
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    public static PrivateKey generatePrivateKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (isBlank(algorithm)) {
            throw new NullPointerException("algorithm must be not null !");
        }
        if (key == null || key.length == 0) {
            throw new NullPointerException("key must be not null !");
        }
        KeySpec keySpec = new PKCS8EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePrivate(keySpec);
    }

    /**
     * 公钥转换为 {@link ECPublicKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPublicKeyParameters publicKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        if (isBlank(algorithm)) {
            throw new NullPointerException("algorithm must be not null !");
        }
        if (key == null || key.length == 0) {
            throw new NullPointerException("key must be not null !");
        }
        PublicKey publicKey = generatePublicKey(algorithm, key);
        return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
    }

    /**
     * 生成公钥
     *
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    public static PublicKey generatePublicKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (isBlank(algorithm)) {
            throw new NullPointerException("algorithm must be not null !");
        }
        if (key == null || key.length == 0) {
            throw new NullPointerException("key must be not null !");
        }
        KeySpec keySpec = new X509EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePublic(keySpec);
    }

    /**
     * 获取用于密钥生成的算法<br>
     * 获取XXXwithXXX算法的后半部分算法，如果为ECDSA或SM2，返回算法为EC
     *
     * @param algorithm XXXwithXXX算法
     * @return 算法
     */
    private static String getAlgorithmAfterWith(String algorithm) {
        if (isBlank(algorithm)) {
            throw new NullPointerException("algorithm must be not null !");
        }
        int indexOfWith = lastIndexOfIgnoreCase(algorithm, "with");
        if (indexOfWith > 0) {
            algorithm = substring(algorithm, indexOfWith + "with".length());
        }
        if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
            algorithm = ALGORITHM;
        }
        return algorithm;
    }

    /**
     * 获取{@link KeyFactory}
     *
     * @param algorithm 非对称加密算法
     * @return {@link KeyFactory}
     */
    private static KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException {
        final Provider provider = new BouncyCastleProvider();
        return KeyFactory.getInstance(algorithm, provider);
    }

    public static void main(String[] args) throws Exception {
        SM2Util sm2Utils = new SM2Util();
        sm2Utils.testSignByQuickpass();
    }

    private void testSignByQuickpass() throws Exception {
        //密钥生成
        String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgOHOzaKWBWOAbslvtYtS8tcQiJ9W9DT2cGQM3KSgXpfygCgYIKoEcz1UBgi2hRANCAAQNPct0MHOuDrVJBIy0RI7JKx9/PTlFHZlkIJr0EqpC+b/+N+z3JKbCMZ/wcP1V36Q+DlfDJCY9Ai/Uk2PZ9ZaF";
        String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEDT3LdDBzrg61SQSMtESOySsffz05RR2ZZCCa9BKqQvm//jfs9ySmwjGf8HD9Vd+kPg5XwyQmPQIv1JNj2fWWhQ==";
        // 加验签
        //String value = "{\"partner\": \"test\",\"tranTime\": \"20240128003627\",\"seqNo\": \"123456\",\"orderNo\": \"order123456\", \"orderAmt\": \"1500\"}";
        String value="{\"data\":{\"companyCode\":\"CN000001\",\"purpose\":\"加入中国光伏行业协会会员单位，获得行业技术支持，2025年会员单位缴纳标准为1万元/年，汇款时需注明“会费”字样，并在备注中注明汇款单位；缴纳会费后，需有付款凭证，将付款凭证发送至协会邮箱，并填写开票信息等内容\",\"recAccountNum\":\"8110701012801540483\",\"recAccountName\":\"\",\"payAccountNum\":\"8110701012601540892\",\"fundType\":\"0001\",\"transAmount\":\"9.90\",\"documentNo\":\"DG202503240321\",\"recBankCode\":\"\",\"settleAccountType\":\"CASH_TRANSFER\",\"sourceFlowNumber\":\"-3854827654841675885_1539\",\"submitUser\":\"user1\",\"digest\":\"加入中国光伏行业协会会员单位，获得行业技术支持，2025年会\",\"payChannel\":\"DIRECT\",\"currency\":\"CNY\",\"toPublic\":true,\"payAccountName\":\"\",\"payDate\":\"2025-03-26\"}}";
        System.out.println("明文：" + value);
        //加密
        //String encryptStr = encrypt(value, publicKey);
        String encryptStr="BLTRQV8d8D1ILWs21weVXVubIB1Ppx3Oy3rWqdocyrAXm5WBNhSz6YemoVVPTTtdycJ3mcFF1YuRKhuHsMhU/VkzssS3i7W8FPwWHNyyorhd3LrP0HMx+j3MN/s7kGs9PGRem/X4U+tQsJxoQSi8CkmwCcF42aN78LkQ5292cDQ6/AcobhKYNHBqCeX9+gUhue76pWFM12k9rXQYvCnIqdf+vB5ot+UXuk3eXwnlmVdP+f5kSSKxyWRIC8GjLwHRWChkuk6IkjIhVlSMJfVt1fOseBYq5VW32XkHQFbSZW3JvYUhHpimibw7SP6SK60TT8lfJzsABJ5euADCFlhVwfUZZXZLdSV4YFSB/IQ6vVkHorQmD5RdGxktgu3MbErdEPhiGl7/tLj6gKi2ecRobGkQwu1peKcKB08WgnWFX/6Wbs0dxfavAAc1W3mXta0zx/+q2jiIydCsySUT2xCRDVBlGHPIlN8RDucQ4mOE5gBlr9vvMxnKOG2kdWzTVoj+aibqKh5ljwGbIUetbE3XtwNlO7deupvTaUN565P1M8cuK7zQjLeoUD2gqLMZOzz5fgkrPTzd7jUyf3QatDOGEFxrF1u03BMJVNAssxj7pdq2aSBTrHKwafoplUpWEAogeiUh2xYlVSEu2BUinHoMxqGU57UM/ZU1IRFgVKTdHV/R/4hLOpu55Mq7C5cOTVwVFasnIdbkJvabNX/vN0js9Bx/2QJx5SN2wqCV5R4yrXTH4dUGVTQFNLuiZSX8TML463FCIrtiofHZ/jvp3hEbyYTchkMuPEvH3MBZAt6ZXmwInYEktymF2nvBD7Pw22xQaE1Sqfjig5m4gQHB1mRV/RKCaNv7CuXsef29ldU5mQnER0U=";
        System.out.println("加密结果：" + encryptStr);

        //解密
        String decryptStr = decrypt(encryptStr, privateKey);
        System.out.println("解密结果：" + decryptStr);
    }

    /**
     * 判断字符串是否为空
     *
     * @param str 字符串
     * @return true/false
     */
    public static boolean isBlank(String str) {
        int strLen;
        if (str != null && (strLen = str.length()) != 0) {
            for (int i = 0; i < strLen; ++i) {
                if (!Character.isWhitespace(str.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }


    public static String substring(final String str, int start) {
        if (str == null) {
            return null;
        }
        // handle negatives, which means last n characters
        if (start < 0) {
            start = str.length() + start; // remember start is negative
        }
        if (start < 0) {
            start = 0;
        }
        if (start > str.length()) {
            return "";
        }
        return str.substring(start);
    }

    public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
        if (str == null || searchStr == null) {
            return -1;
        }
        return lastIndexOfIgnoreCase(str, searchStr, str.length());
    }

    public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) {
        if (str == null || searchStr == null) {
            return -1;
        }
        if (startPos > str.length() - searchStr.length()) {
            startPos = str.length() - searchStr.length();
        }
        if (startPos < 0) {
            return -1;
        }
        if (searchStr.length() == 0) {
            return startPos;
        }
        for (int i = startPos; i >= 0; i--) {
            if (regionMatches(str, i, searchStr, searchStr.length())) {
                return i;
            }
        }
        return -1;
    }

    static boolean regionMatches(final CharSequence cs, final int thisStart,
                                 final CharSequence substring, final int length) {
        if (cs instanceof String && substring instanceof String) {
            return ((String) cs).regionMatches(true, thisStart, (String) substring, 0, length);
        }
        int index1 = thisStart;
        int index2 = 0;
        int tmpLen = length;
        // Extract these first so we detect NPEs the same as the java.lang.String version
        final int srcLen = cs.length() - thisStart;
        final int otherLen = substring.length();
        // Check for invalid parameters
        if (thisStart < 0 || length < 0) {
            return false;
        }
        // Check that the regions are long enough
        if (srcLen < length || otherLen < length) {
            return false;
        }
        while (tmpLen-- > 0) {
            final char c1 = cs.charAt(index1++);
            final char c2 = substring.charAt(index2++);

            if (c1 == c2) {
                continue;
            }
            // The same check as in String.regionMatches():
            if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
                    && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
                return false;
            }
        }
        return true;
    }
}
