use ave_common::identity::KeyPair;
use pkcs8::{Document, EncryptedPrivateKeyInfo, PrivateKeyInfo, pkcs5};

use getrandom::fill;
use std::fs;

use crate::config::Config;
use crate::error::BridgeError;

const PBKDF2_ITERATIONS: u32 = 200_000;

pub fn key_pair(
    config: &Config,
    password: &str,
) -> Result<KeyPair, BridgeError> {
    if fs::metadata(&config.keys_path).is_err() {
        fs::create_dir_all(&config.keys_path)
            .map_err(|e| BridgeError::KeyDirectoryCreation(e.to_string()))?;
    }

    let path = config.keys_path.join("node_private.der");
    match fs::metadata(&path) {
        Ok(_) => {
            let document = Document::read_der_file(path)
                .map_err(|e| BridgeError::KeyRead(e.to_string()))?;
            let enc_pk = EncryptedPrivateKeyInfo::try_from(document.as_bytes())
                .map_err(|e| BridgeError::KeyRead(e.to_string()))?;
            let dec_pk = enc_pk
                .decrypt(password)
                .map_err(|e| BridgeError::KeyDecrypt(e.to_string()))?;

            let key_pair = KeyPair::from_secret_der(dec_pk.as_bytes())
                .map_err(|e| BridgeError::KeyRestore(e.to_string()))?;
            Ok(key_pair)
        }
        Err(_) => {
            let key_pair = config
                .node
                .keypair_algorithm
                .generate_keypair()
                .map_err(|e| BridgeError::KeyGeneration(e.to_string()))?;

            let der = key_pair
                .to_secret_der()
                .map_err(|e| BridgeError::KeyGeneration(e.to_string()))?;
            let pk = PrivateKeyInfo::try_from(der.as_slice())
                .map_err(|e| BridgeError::KeyGeneration(e.to_string()))?;
            let mut salt = [0u8; 32];
            let mut iv = [0u8; 16];
            fill(&mut salt)
                .map_err(|e| BridgeError::KeyEncrypt(e.to_string()))?;
            fill(&mut iv)
                .map_err(|e| BridgeError::KeyEncrypt(e.to_string()))?;

            let params = pkcs5::pbes2::Parameters::pbkdf2_sha256_aes256cbc(
                PBKDF2_ITERATIONS,
                &salt,
                &iv,
            )
            .map_err(|e| BridgeError::KeyEncrypt(e.to_string()))?;
            let enc_pk =
                pk.encrypt_with_params(params, password).map_err(|_| {
                    BridgeError::KeyEncrypt(
                        "encryption algorithm failed".to_owned(),
                    )
                })?;
            enc_pk
                .write_der_file(path)
                .map_err(|e| BridgeError::KeyWrite(e.to_string()))?;
            Ok(key_pair)
        }
    }
}
