use std::path::PathBuf;

use config::SideNodeConfig;

use crate::{keys, utils};

pub(crate) mod config;

pub(crate) fn init(home: PathBuf, config: SideNodeConfig) -> Result<(), std::io::Error> {
    ensure_side_directory_exists(&home)?;
    let (key_path, config_path) = utils::side_paths(home.clone());

    println!("Writing key to: {:?}", key_path);
    keys::write(key_path)?;

    println!("Writing config to: {:?}", config_path);
    config::write_toml(&config, &config_path).expect("unable to write config file");

    Ok(())
}

/// Ensures that the directory at side_dir exists, so we have a place
/// to store our key file and config file.
fn ensure_side_directory_exists(side_dir: &PathBuf) -> Result<(), std::io::Error> {
    if side_dir.exists() {
        return Ok(());
    }
    println!(
        "Config directory doesn't exist, creating at: {:?}",
        side_dir
    );
    std::fs::create_dir_all(side_dir)
}

#[cfg(test)]
mod tests {
    use std::{fs, path::Path, str::FromStr};

    use fastcrypto::{
        ed25519::Ed25519KeyPair,
        traits::{EncodeDecodeBase64, KeyPair, ToFromBytes},
    };

    use super::*;

    /// Generates a SideNodeConfig with a unique name for each test.
    /// This is necessary because the tests run in parallel and we
    /// don't want them to interfere with each other - without a unique
    /// name, the tests would all try to write to the same directory and we
    /// get test indeterminacy
    fn side_node_config() -> (SideNodeConfig, String) {
        let name = format!("test-{}", uuid::Uuid::new_v4()).to_string();
        (SideNodeConfig { name: name.clone() }, name)
    }

    #[test]
    fn creates_side_node_directory() {
        let (config, name) = side_node_config();
        let side_dir = format!("/tmp/side/{name}");

        let mut test_home = PathBuf::new();

        test_home.push(side_dir);
        let node_dir = Path::new(&test_home).parent().unwrap().to_str().unwrap();
        let _ = init(test_home.clone(), config);
        assert!(std::path::Path::new(node_dir).exists());
    }

    #[test]
    fn creates_key_file() {
        let (config, name) = side_node_config();
        let side_dir = format!("/tmp/side/{name}");

        let mut key_file_path = PathBuf::new();
        key_file_path.push(side_dir.clone());
        key_file_path.push(utils::KEY_FILE);

        let _ = init(PathBuf::from_str(&side_dir).unwrap(), config);
        assert!(key_file_path.exists());

        // check that the pem is readable
        let data = fs::read_to_string(key_file_path).expect("couldn't read key file");
        let keys = Ed25519KeyPair::decode_base64(&data).expect("couldn't load keypair from file");
        assert_eq!(keys.public().as_bytes().len(), 32);
    }

    #[test]
    fn creates_config_file() {
        let (config, name) = side_node_config();
        let side_dir = format!("/tmp/side/{name}");

        let mut config_file_path = PathBuf::new();
        config_file_path.push(side_dir.clone());
        config_file_path.push(utils::CONFIG_FILE);

        let _ = init(PathBuf::from_str(&side_dir).unwrap(), config);
        assert!(config_file_path.exists());
    }
}
