package com.jsowell.common.util;

import com.alibaba.fastjson2.JSON;
import com.jsowell.common.util.http.HttpUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.alibaba.fastjson2.JSONObject;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;

/**
 * 国密二加密 Util
 *
 * @author Lemon
 * @Date 2024/5/7 10:23:56
 */
public class Sm2Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 随机生成sm2的公钥私钥对
     *
     * @return 公钥私钥对
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            SecureRandom random = new SecureRandom();
            // 使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec, random);
            // 获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            return keyPair;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param data                    加密前的json数据
     * @param platformPublicKeyStr    点行平台的国密二公钥
     * @param thirdPartyPrivateKeyStr 第三方平台生成的国密二私钥
     * @return 用于http发送请求的数据
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchProviderException
     * @throws CryptoException
     */
    public static String generateEncryptedRequestInfo(JSONObject data, String platformPublicKeyStr, String thirdPartyPrivateKeyStr)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, CryptoException {
        String sign = sign(thirdPartyPrivateKeyStr, data);
        data.put("sign", sign);
        return encrypt(platformPublicKeyStr, data.toString());
    }

    /**
     * @param publicKeyStr 以BASE64表示的sm2公钥
     * @param data         待加密字符串， UTF-8编码
     * @return 以BASE64表示的加密结果
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    private static String encrypt(String publicKeyStr, String data) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        BCECPublicKey publicKey = getPublicKey(Base64.decode(publicKeyStr));
        //通过公钥对象获取公钥的基本域参数。
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //根据加密模式实例化SM2公钥加密引擎
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        //初始化加密引擎
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfBytes = null;
        try {
            //将明文字符串转换为指定编码的字节串
            byte[] in = data.getBytes("utf-8");
            //通过加密引擎对字节数串行加密
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //将加密后的字节串转换为BASE 64字符串
        return Base64.toBase64String(arrayOfBytes);
    }

    /**
     * @param privateKeyStr 以BASE64表示的sm2私钥
     * @param cipherData    以BASE64表示的加密结果
     * @return 解密后字符串， UTF-8编码
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    private static String decrypt(String privateKeyStr, String cipherData) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        BCECPrivateKey privateKey = getPrivateKey(Base64.decode(privateKeyStr));
        byte[] cipherDataByte = Base64.decode(cipherData);
        //通过私钥对象获取私钥的基本域参数。
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过私钥值和私钥钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);
        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, ecPrivateKeyParameters);
        String result = null;
        try {
            //通过解密引擎对密文字节串进行解密
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            //将解密后的字节串转换为utf8字符编码的字符串（需要与明文加密时字符串转换成字节串所指定的字符编码保持一致）
            result = new String(arrayOfBytes, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @param privateKeyStr 以BASE64表示的sm2私钥
     * @param dataJson      待签名json数据
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws CryptoException
     */
    private static String sign(String privateKeyStr, JSONObject dataJson) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, CryptoException {
        BCECPrivateKey privateKey = getPrivateKey(Base64.decode(privateKeyStr));
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过私钥值和私钥钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);

        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();

        //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
        try {
            sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(ecPrivateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String content = getSignContent(dataJson);
        byte[] message = content.getBytes();
        sm2Signer.update(message, 0, message.length);
        //生成签名,签名分为两部分r和s,分别对应索引0和1的数组
        byte[] signBytes = sm2Signer.generateSignature();
        return Base64.toBase64String(signBytes);
    }

    /**
     * @param publicKeyStr 以BASE64表示的sm2公钥
     * @param dataJson     含有签名的待验签json数据
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    private static boolean verify(String publicKeyStr, JSONObject dataJson) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        BCECPublicKey publicKey = getPublicKey(Base64.decode(publicKeyStr));
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        ParametersWithID parametersWithID = new ParametersWithID(ecPublicKeyParameters, Strings.toByteArray("1234567812345678"));
        sm2Signer.init(false, parametersWithID);
        String sign = (String) dataJson.remove("sign");
        String content = getSignContent(dataJson);
        byte[] message = content.getBytes();
        sm2Signer.update(message, 0, message.length);
        //验证签名结果
        return sm2Signer.verifySignature(Base64.decode(sign));
    }

    // private static String getSignContent(JSONObject rawData) {
    //     JSONObject data = new JSONObject(new TreeMap<>());
    //     rawData.forEach((k, v) -> data.put(k, v));
    //     StringBuffer sb = new StringBuffer();
    //     data.forEach((k, v) -> {
    //         if (v != null && !"".equals(v)) {
    //             sb.append(k + "=" + v + "&");
    //         }
    //     });
    //     String stringData = sb.toString();
    //     return stringData == null || stringData.isEmpty() ? "" : stringData.substring(0, stringData.length() - 1);
    // }

    private static String getSignContent(JSONObject rawData) {
        StringBuilder content = new StringBuilder();
        List<String> keys = new ArrayList<>(rawData.keySet());
        // 将参数集合排序
        Collections.sort(keys);
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = (String) rawData.get(key);
            // 拼装所有非空参数
            if (key != null && !"".equalsIgnoreCase(key) && value != null && !"".equalsIgnoreCase(value)) {
                content.append(i == 0 ? "" : "&").append(key).append("=").append(value);
            }
        }
        return content.toString();
    }

    private static BCECPrivateKey getPrivateKey(byte[] privateBytes) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
        PKCS8EncodedKeySpec eks2 = new PKCS8EncodedKeySpec(privateBytes);
        KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(eks2);
    }

    private static BCECPublicKey getPublicKey(byte[] publicBytes) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec eks = new X509EncodedKeySpec(publicBytes);
        KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPublicKey) kf.generatePublic(eks);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, CryptoException {
        // KeyPair keyPair = generateSm2KeyPair();
        // System.out.println(Base64.toBase64String(keyPair.getPublic().getEncoded()));
        // System.out.println(Base64.toBase64String(keyPair.getPrivate().getEncoded()));
        String platformPublicKeyStr = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEYPOlKmr/XY+na8KxiNvRui1esFugt4tT2AVk+eRlH4KCYLabDZDordal3kcn4UNM7t6J+dyhcfLstNWXpf4lQA==";
        String thirdPartyPrivateKeyStr = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgCMref1FGlPZ9RfeJw/cnU5uEvFZNhHt7OvF4sgXnBjWgCgYIKoEcz1UBgi2hRANCAARj6kqkCaeNJSxWExQFsot1OuSCFrQOblhKx0U/y8GhgSND2MOAM08yXzl308waLqLt+jcsLF2UTW6XfrZNS5pk";
        JSONObject data = new JSONObject();
        data.put("outTradeId","202212231641900215435");
        data.put("plateNum","浙B12345");
        data.put("startTime","2023-01-09 10:00:01");
        data.put("endTime","2023-01-09 11:00:02");
        data.put("recordTime","2023-01-09 12:00:03");
        data.put("payAmount","12.34");
        System.out.println(data);
        String recordInfoStr = generateEncryptedRequestInfo(data, platformPublicKeyStr, thirdPartyPrivateKeyStr);
        String tpToken = "4121d1c9121a41d894ed5082d264db30";

        // 发送请求
        String url = "http://123.60.34.253:9527/parking-shop/tp/chargeRecord/report";
        JSONObject postParam = new JSONObject();
        postParam.put("recordInfo", recordInfoStr);

        String result = HttpUtils.sendPostTpToken(url, postParam.toJSONString(), tpToken); // {"code":103,"msg":"数据验签不通过","data":null}
        // String result = HttpUtil.post(url, postParam.toJSONString());

        System.out.println(result);
    }

    public static JSONObject getPlaintextRequestInfo(String encryptedString, String platformPrivateKeyStr, String thirdPartyPublicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
        String plaintext = decrypt(platformPrivateKeyStr, encryptedString);
        JSONObject result = JSON.parseObject(plaintext);
        if(verify(thirdPartyPublicKeyStr, result)){
            return result;
        }else{
            return null;
        }
    }
}
