use std::error::Error;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::{Child, Command};
use log::debug;
use crate::dirs::get_data_dir;
use crate::util::sha256;

pub fn extract_embedded_nebula() -> Result<PathBuf, Box<dyn Error>> {
    let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?;
    if !data_dir.exists() {
        fs::create_dir_all(&data_dir)?;
        debug!("Created data directory {}", data_dir.as_path().display());
    }

    let bin_dir = data_dir.join("cache/");
    let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_bin::NEBULA_BIN)));

    if !hash_dir.exists() {
        fs::create_dir_all(&hash_dir)?;
        debug!("Created directory {}", hash_dir.as_path().display());
    }

    let executable_postfix = if cfg!(windows) { ".exe" } else { "" };
    let executable_name = format!("nebula-{}{}", crate::nebula_bin::NEBULA_VERSION, executable_postfix);

    let file_path = hash_dir.join(executable_name);

    if file_path.exists() {
        // Already extracted
        return Ok(file_path);
    }
    let mut file = File::create(&file_path)?;
    file.write_all(crate::nebula_bin::NEBULA_BIN)?;

    debug!("Extracted nebula to {}", file_path.as_path().display());

    Ok(file_path)
}

pub fn extract_embedded_nebula_cert() -> Result<PathBuf, Box<dyn Error>> {
    let data_dir = get_data_dir().ok_or("Unable to get platform-specific data dir")?;
    if !data_dir.exists() {
        fs::create_dir_all(&data_dir)?;
        debug!("Created data directory {}", data_dir.as_path().display());
    }

    let bin_dir = data_dir.join("cache/");
    let hash_dir = bin_dir.join(format!("{}/", sha256(crate::nebula_cert_bin::NEBULA_CERT_BIN)));

    if !hash_dir.exists() {
        fs::create_dir_all(&hash_dir)?;
        debug!("Created directory {}", hash_dir.as_path().display());
    }

    let executable_postfix = if cfg!(windows) { ".exe" } else { "" };
    let executable_name = format!("nebula-cert-{}{}", crate::nebula_cert_bin::NEBULA_CERT_VERSION, executable_postfix);

    let file_path = hash_dir.join(executable_name);

    if file_path.exists() {
        // Already extracted
        return Ok(file_path);
    }

    let mut file = File::create(&file_path)?;
    file.write_all(crate::nebula_cert_bin::NEBULA_CERT_BIN)?;

    debug!("Extracted nebula-cert to {}", file_path.as_path().display());

    Ok(file_path)
}

#[cfg(unix)]
pub fn _setup_permissions(path: &PathBuf) -> Result<(), Box<dyn Error>> {
    let meta = path.metadata()?;
    let mut perms = meta.permissions();
    perms.set_mode(0o0755);
    debug!("Setting permissions on {} to 755", path.as_path().display());
    fs::set_permissions(path, perms)?;
    Ok(())
}

#[cfg(windows)]
pub fn _setup_permissions() -> Result<(), Box<dyn Error>> {
    Ok(())
}

pub fn run_embedded_nebula(args: &[String]) -> Result<Child, Box<dyn Error>> {
    let path = extract_embedded_nebula()?;
    debug!("Running {} with args {:?}", path.as_path().display(), args);
    _setup_permissions(&path)?;
    Ok(Command::new(path).args(args).spawn()?)
}

pub fn run_embedded_nebula_cert(args: &[String]) -> Result<Child, Box<dyn Error>> {
    let path = extract_embedded_nebula_cert()?;
    debug!("Running {} with args {:?}", path.as_path().display(), args);
    _setup_permissions(&path)?;
    Ok(Command::new(path).args(args).spawn()?)
}