package common.utils.hyg;

import com.jfinal.kit.StrKit;
import common.jfinal.AppConfig;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class RSAUtils {
    // 密钥算法
    public static final String KEY_ALGORITHM = "RSA";
    // 签名算法
    public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
    // 加密/解密算法及填充方式, RSA默认使用PKCS1Padding
    public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";

    // 密钥长度，这里使用1024位
    private static final int KEY_SIZE = 1024; // 也可以是 2048 等

    // 存储公钥和私钥的Map键
    private static final String PUBLIC_KEY = "RSAPublicyKey";
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    // RSA加密最大分段大小 (对于1024位RSA密钥，PKCS1Padding填充，最大加密长度为128-11=117字节)
    private static final int MAX_ENCRYPT_BLOCK = KEY_SIZE / 8 - 11; // 1024/8 - 11 = 128 - 11 = 117
    // RSA解密最大分段大小 (对于1024位RSA密钥，解密时最大分段大小为128字节)
    private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8; // 1024/8 = 128

    // 私有构造器，防止实例化
    private RSAUtils() {
    }

    /**
     * 生成RSA密钥对
     *
     * @return 包含公钥和私钥的Map，公钥键为"RSAPublicKey"，私钥键为"RSAPrivateKey"
     * @throws Exception 如果生成密钥对失败
     */
    public static Map<String, Key> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(KEY_SIZE);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Key> keyMap = new HashMap<>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 对数据进行数字签名
     *
     * @param data       待签名的数据字节数组
     * @param privateKey Base64编码的私钥字符串
     * @return Base64编码的签名字符串
     * @throws Exception 如果签名过程中发生错误
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); // 可能会在此处抛出 InvalidKeySpecException

        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 验证数字签名
     *
     * @param data      原始数据字节数组
     * @param publicKey Base64编码的公钥字符串
     * @param sign      Base64编码的签名字符串
     * @return true表示签名有效，false表示签名无效
     * @throws Exception 如果验证签名过程中发生错误
     */
    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicK = keyFactory.generatePublic(keySpec);

        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicK);
        signature.update(data);
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    /**
     * 私钥解密
     *
     * @param encryptedData 待解密的密文字节数组
     * @param privateKey    Base64编码的私钥字符串
     * @return 解密后的原始数据字节数组
     * @throws Exception 如果解密过程中发生错误
     */
    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            int actualBlockSize = (inputLen - offSet > MAX_DECRYPT_BLOCK) ? MAX_DECRYPT_BLOCK : (inputLen - offSet);
            cache = cipher.doFinal(encryptedData, offSet, actualBlockSize);
            out.write(cache, 0, cache.length);
            offSet += actualBlockSize; // 修正 offSet 推进逻辑
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥解密（在实际应用中，通常是私钥加密，公钥解密；或公钥加密，私钥解密。公钥解密不常见，除非是验证私钥签名）
     * 注意：RSA公钥解密通常是用来验证由私钥进行加密（签名）的数据，并非用于加密数据的机密性。
     *
     * @param encryptedData 待解密的密文字节数组
     * @param publicKey     Base64编码的公钥字符串
     * @return 解密后的原始数据字节数组
     * @throws Exception 如果解密过程中发生错误
     */
    public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicK);

        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            int actualBlockSize = (inputLen - offSet > MAX_DECRYPT_BLOCK) ? MAX_DECRYPT_BLOCK : (inputLen - offSet);
            cache = cipher.doFinal(encryptedData, offSet, actualBlockSize);
            out.write(cache, 0, cache.length);
            offSet += actualBlockSize; // 修正 offSet 推进逻辑
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥加密
     *
     * @param data      待加密的原始数据字节数组
     * @param publicKey Base64编码的公钥字符串
     * @return 加密后的密文字节数组
     * @throws Exception 如果加密过程中发生错误
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            int actualBlockSize = (inputLen - offSet > MAX_ENCRYPT_BLOCK) ? MAX_ENCRYPT_BLOCK : (inputLen - offSet);
            cache = cipher.doFinal(data, offSet, actualBlockSize);
            out.write(cache, 0, cache.length);
            offSet += actualBlockSize; // 修正 offSet 推进逻辑 (这里是 inputLen)
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 私钥加密（在实际应用中，通常用于签名，而非加密数据机密性）
     * 注意：RSA私钥加密通常用于生成数字签名，而不是为了数据机密性。
     *
     * @param data       待加密的原始数据字节数组
     * @param privateKey Base64编码的私钥字符串
     * @return 加密后的密文字节数组
     * @throws Exception 如果加密过程中发生错误
     */
    public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateK);

        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            int actualBlockSize = (inputLen - offSet > MAX_ENCRYPT_BLOCK) ? MAX_ENCRYPT_BLOCK : (inputLen - offSet);
            cache = cipher.doFinal(data, offSet, actualBlockSize);
            out.write(cache, 0, cache.length);
            offSet += actualBlockSize; // 修正 offSet 推进逻辑 (这里是 inputLen)
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 从密钥Map中获取Base64编码的私钥字符串
     *
     * @param keyMap 包含私钥的Map
     * @return Base64编码的私钥字符串
     * @throws Exception 如果获取私钥失败
     */
    public static String getPrivateKey(Map<String, Key> keyMap) throws Exception {
        Key key = keyMap.get(PRIVATE_KEY);
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }

    /**
     * 从密钥Map中获取Base64编码的公钥字符串
     *
     * @param keyMap 包含公钥的Map
     * @return Base64编码的公钥字符串
     * @throws Exception 如果获取公钥失败
     */
    public static String getPublicKey(Map<String, Key> keyMap) throws Exception {
        Key key = keyMap.get(PUBLIC_KEY);
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }

    /**
     * 将参数名称按照ASCII排序并拼接成字符串
     * 用于生成待签名字符串，通常用于接口请求参数签名校验
     *
     * @param paramMap 包含参数的Map
     * @return 排序并拼接后的字符串
     */
    public static String sortParam(Map<String, Object> paramMap) {
        if (paramMap == null || paramMap.isEmpty()) {
            return "";
        }
        List<Map.Entry<String, Object>> list = new ArrayList<>(paramMap.entrySet());
        Collections.sort(list, Comparator.comparing(Map.Entry::getKey));

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : list) {
            if (!StrKit.isBlank(item.getKey()) && item.getValue() != null) {
                sb.append(item.getKey()).append("=").append(item.getValue()).append("&");
            }
        }
        if (sb.length() > 0) {
            return sb.substring(0, sb.length() - 1);
        }
        return "";
    }

    public static void main(String[] args) {
        try {
            // 1. 生成RSA密钥对
            Map<String, Key> keyMap = genKeyPair();
            String publicKey = getPublicKey(keyMap);
            String privateKey = getPrivateKey(keyMap);

            AppConfig.LOGGER.info("--- RSA 密钥对生成 ---");
            AppConfig.LOGGER.info("公钥 (Base64编码):\n" + publicKey);
            AppConfig.LOGGER.info("私钥 (Base64编码):\n" + privateKey);
            AppConfig.LOGGER.info("----------------------\n");

            String originalContent = "这是一段需要使用RSA加密和签名的中文文本，包含较长的内容，以测试分段处理。Hello World! 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            AppConfig.LOGGER.info("原始数据: " + originalContent);
            byte[] dataBytes = originalContent.getBytes(StandardCharsets.UTF_8);

            // 2. 公钥加密，私钥解密 （实现数据机密性）
            AppConfig.LOGGER.info("--- 公钥加密，私钥解密 ---");
            byte[] encryptedByPublicKey = encryptByPublicKey(dataBytes, publicKey);
            AppConfig.LOGGER.info("公钥加密后的数据 (Base64编码): " + Base64.getEncoder().encodeToString(encryptedByPublicKey));

            byte[] decryptedByPrivateKey = decryptByPrivateKey(encryptedByPublicKey, privateKey);
            String decryptedString = new String(decryptedByPrivateKey, StandardCharsets.UTF_8);
            AppConfig.LOGGER.info("私钥解密后的数据: " + decryptedString);
            AppConfig.LOGGER.info("验证一致性: " + originalContent.equals(decryptedString));
            AppConfig.LOGGER.info("----------------------\n");

            // 3. 私钥加密，公钥解密 （实现数字签名，通常不用于传输数据）
            AppConfig.LOGGER.info("--- 私钥加密，公钥解密 (常用于签名验证) ---");
            byte[] encryptedByPrivateKey = encryptByPrivateKey(dataBytes, privateKey);
            AppConfig.LOGGER.info("私钥加密后的数据 (Base64编码): " + Base64.getEncoder().encodeToString(encryptedByPrivateKey));

            byte[] decryptedByPublicKey = decryptByPublicKey(encryptedByPrivateKey, publicKey);
            String decryptedString2 = new String(decryptedByPublicKey, StandardCharsets.UTF_8);
            AppConfig.LOGGER.info("公钥解密后的数据: " + decryptedString2);
            AppConfig.LOGGER.info("验证一致性: " + originalContent.equals(decryptedString2));
            AppConfig.LOGGER.info("----------------------\n");

            // 4. 数字签名与验证 (主流用法)
            AppConfig.LOGGER.info("--- 数字签名与验证 ---");
            String sign = sign(dataBytes, privateKey);
            AppConfig.LOGGER.info("生成的签名 (Base64编码): " + sign);

            // 验证正确的签名
            boolean verified = verify(dataBytes, publicKey, sign);
            AppConfig.LOGGER.info("验证签名结果 (正确数据与签名): " + verified);

            // 验证篡改后的数据
            byte[] tamperedData = "这是被篡改的数据".getBytes(StandardCharsets.UTF_8);
            boolean tamperedVerified = verify(tamperedData, publicKey, sign);
            AppConfig.LOGGER.info("验证签名结果 (篡改数据与原签名): " + tamperedVerified);

            // 验证错误的签名
            String wrongSign = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            try {
                boolean wrongSignVerified = verify(dataBytes, publicKey, wrongSign);
                AppConfig.LOGGER.info("验证签名结果 (正确数据与错误签名): " + wrongSignVerified);
            } catch (IllegalArgumentException e) {
                AppConfig.LOGGER.info("验证签名结果 (正确数据与错误签名): Base64解码错误，签名非法。");
            }

            AppConfig.LOGGER.info("----------------------\n");

            // 5. sortParam 方法测试
            AppConfig.LOGGER.info("--- sortParam 方法测试 ---");
            Map<String, Object> params = new TreeMap<>();
            params.put("appKey", "123");
            params.put("timestamp", System.currentTimeMillis());
            params.put("paramA", "valueA");
            params.put("paramC", "valueC");
            params.put("paramB", null);
            params.put("paramD", ""); // 测试空字符串

            String sortedAndConcatenated = sortParam(params);
            AppConfig.LOGGER.info("原始参数Map: " + params);
            AppConfig.LOGGER.info("排序拼接结果: " + sortedAndConcatenated);

            AppConfig.LOGGER.info("----------------------\n");

        } catch (Exception e) {
            AppConfig.LOGGER.error("发生了一个错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}