package com.cpop.core.utils;

import com.cpop.core.base.exception.UtilException;
import com.cpop.core.config.GenerateKeyPairConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author DB
 * @Description: Rsa相关工具类
 * @create 2023-08-04 23:09
 */
@Component
public class RsaUtils {

    @Autowired
    private GenerateKeyPairConfig keyPairConfig;

    // region 私有方法

    /**
     * 生成密钥对
     * @return 返回map集合，其中包含publicKey与privateKey
     * @throws NoSuchAlgorithmException
     */
    private Map<String, Key> generateKeyPair() throws NoSuchAlgorithmException {
        //RSA算法要求有一个可信任的随机数源
        SecureRandom secureRandom = new SecureRandom();
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyPairConfig.getAlgorithm());
        //利用上面的随机数据源初始化这个KeyPairGenerator对象
        keyPairGenerator.initialize(keyPairConfig.getKeySize(), secureRandom);
        //生成密匙对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //得到公钥
        Key publicKey = keyPair.getPublic();
        //得到私钥
        Key privateKey = keyPair.getPrivate();
        Map<String, Key> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKey);
        keyPairMap.put("privateKey", privateKey);
        return keyPairMap;
    }

    /**
     * 获取文件中获取密钥对象
     * @param fileName 文件名
     * @return 密钥对象
     */
    private Key getKeyFromFile(String fileName) {
        Key key = null;
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)));
            key = (Key) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new UtilException(e.getMessage());
        } finally {
            try {
                assert ois != null;
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return key;
    }

    /**
     * 将密钥对生成到文件中
     */
    private void generateKeyPairToFiles() {
        generateKeyPairToFiles(keyPairConfig.getPublicKeyFile(),keyPairConfig.getPrivateKeyFile());
    }

    private void generateKeyPairToFiles(String publicKeyFile, String privateKeyFile) {
        ObjectOutputStream oosPublicKey = null;
        ObjectOutputStream oosPrivateKey = null;
        try {
            Map<String, Key> keyPairMap = generateKeyPair();
            Key publicKey = keyPairMap.get("publicKey");
            Key privateKey = keyPairMap.get("privateKey");
            oosPublicKey = new ObjectOutputStream(Files.newOutputStream(Paths.get(publicKeyFile)));
            oosPrivateKey = new ObjectOutputStream(Files.newOutputStream(Paths.get(privateKeyFile)));
            oosPublicKey.writeObject(publicKey);
            oosPrivateKey.writeObject(privateKey);
        } catch (NoSuchAlgorithmException | IOException e) {
            throw new UtilException(e.getMessage());
        } finally {
            try {
                //清空缓存，关闭文件输出流
                assert oosPublicKey != null;
                oosPublicKey.close();
                assert oosPrivateKey != null;
                oosPrivateKey.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 初始化密钥对文件
     * @return 返回密钥对信息【publicKey（公钥字符串）、、privateKey（私钥字符串）】
     */
    public Map<String, String> initKeyPair() {
        return initKeyPair(keyPairConfig.getPublicKeyFile(),keyPairConfig.getPrivateKeyFile());
    }

    public Map<String, String> initKeyPair(String publicKeyFile, String privateKeyFile){
        Map<String, String> keyPairMap = new HashMap<>();
        File publicFile = new File(publicKeyFile);
        File privateFile = new File(privateKeyFile);
        //判断是否存在公钥和私钥文件
        if (!publicFile.exists() || !privateFile.exists()) {
            generateKeyPairToFiles(publicKeyFile, privateKeyFile);
        }
        ObjectInputStream oisPublic = null;
        ObjectInputStream oisPrivate = null;
        Key publicKey = null;
        Key privateKey = null;
        try {
            oisPublic = new ObjectInputStream(Files.newInputStream(Paths.get(publicKeyFile)));
            oisPrivate = new ObjectInputStream(Files.newInputStream(Paths.get(privateKeyFile)));
            publicKey = (Key) oisPublic.readObject();
            privateKey = (Key) oisPrivate.readObject();
            byte[] publicKeyBytes = publicKey.getEncoded();
            byte[] privateKeyBytes = privateKey.getEncoded();
            String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKeyBytes);
            String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKeyBytes);
            //公钥字符串
            keyPairMap.put("publicKey", publicKeyBase64);
            //私钥字符串
            keyPairMap.put("privateKey", privateKeyBase64);
        } catch (IOException | ClassNotFoundException e) {
            throw new UtilException(e.getMessage());
        } finally {
            try {
                assert oisPrivate != null;
                oisPrivate.close();
                oisPublic.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return keyPairMap;
    }

    /**
     * 加密方法
     * @param source 源数据
     * @return 加密后的字符串
     */
    public String encrypt(String source, Key publicKey) {
        Base64.Encoder encoder = Base64.getEncoder();
        String encryptSource = null;
        try {
            //得到Cipher对象来实现对源数据的RSA加密
            Cipher cipher = Cipher.getInstance(keyPairConfig.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] bytes = source.getBytes();
            //执行加密操作
            encryptSource = encoder.encodeToString(cipher.doFinal(bytes));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new UtilException(e.getMessage());
        }
        return encryptSource;
    }

    /**
     * 加密方法
     * @param source 源数据
     * @return 加密后的字符串
     */
    public String encrypt(String source) {
        return encrypt(source, getKeyFromFile(keyPairConfig.getPublicKeyFile()));
    }

    /**
     * 解密方法
     * @param source 密文
     * @return 解密后的字符串
     */
    public String decrypt(String source) {
        return decrypt(source,keyPairConfig.getPrivateKeyFile());
    }

    public String decrypt(String source, String privateKeyFile) {
        Key privateKey = getKeyFromFile(privateKeyFile);
        Base64.Decoder decoder = Base64.getDecoder();
        String decryptSource = null;
        try {
            //得到Cipher对象对已用公钥加密的数据进行RSA解密
            Cipher cipher = Cipher.getInstance(keyPairConfig.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            //执行解密操作
            byte[] bytes = decoder.decode(source);
            decryptSource = new String(cipher.doFinal(bytes), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
                 IllegalBlockSizeException | BadPaddingException e) {
            throw new UtilException(e.getMessage());
        }
        return decryptSource;
    }

    public String decrypt(String source, Key privateKey) {
        Base64.Decoder decoder = Base64.getDecoder();
        String decryptSource = null;
        try {
            //得到Cipher对象对已用公钥加密的数据进行RSA解密
            Cipher cipher = Cipher.getInstance(keyPairConfig.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            //执行解密操作
            byte[] bytes = decoder.decode(source);
            decryptSource = new String(cipher.doFinal(bytes), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
                 IllegalBlockSizeException | BadPaddingException e) {
            throw new UtilException(e.getMessage());
        }
        return decryptSource;
    }

}
