use crate::config;
use crate::{Error, PlaceholderMap, Result};

pub struct ProcessConfig {
    path: std::path::PathBuf,
    arguments: Vec<String>,
    environment_variables: std::collections::HashMap<String, String>,
    working_directory: Option<String>,
    uid: Option<u32>,
    gid: Option<u32>,
    stdout_max: u32,
    stderr_max: u32,
}

pub struct ProcessResult {
    pub code: u8,
    pub stdout: String,
    pub stderr: String,
}

impl ProcessConfig {
    pub fn file_name(&self) -> Result<&str> {
        let error_str = "Could not parse file name from path.";
        self.path
            .file_name()
            .ok_or_else(|| Error(error_str.into()))?
            .to_str()
            .ok_or_else(|| Error(error_str.into()))
    }

    pub async fn run(&self, placeholders: Option<PlaceholderMap>) -> Result<ProcessResult> {
        let mut command = tokio::process::Command::new(&self.path);
        command.kill_on_drop(true);
        if let Some(placeholders) = placeholders {
            for argument in self.arguments.iter() {
                let argument = crate::fill_placeholders(argument.as_str(), &placeholders);
                command.arg(argument);
            }
            for (name, value) in self.environment_variables.iter() {
                let name = crate::fill_placeholders(name.as_str(), &placeholders);
                let value = crate::fill_placeholders(value.as_str(), &placeholders);
                command.env(name, value);
            }
        } else {
            command.args(&self.arguments);
            command.envs(&self.environment_variables);
        }
        if let Some(working_directory) = &self.working_directory {
            command.current_dir(working_directory);
        }
        if let Some(uid) = self.uid {
            command.uid(uid);
        }
        if let Some(gid) = self.gid {
            command.gid(gid);
        }
        command.env_remove("NOTIFY_SOCKET");
        log::debug!("Calling process: {}", self.path.display());
        let mut output = command
            .output()
            .await
            .map_err(|x| Error(format!("Failed to run process: {x}")))?;

        match output.status.code() {
            Some(code) => {
                let code = (code & 0xff) as u8;
                output.stdout.truncate(self.stdout_max as usize);
                output.stderr.truncate(self.stderr_max as usize);
                Ok(ProcessResult {
                    code,
                    stdout: String::from_utf8_lossy(&output.stdout[..])
                        .trim()
                        .to_owned(),
                    stderr: String::from_utf8_lossy(&output.stderr[..])
                        .trim()
                        .to_owned(),
                })
            }
            None => Err(Error(String::from("Process was terminated by a signal."))),
        }
    }

    pub fn new(
        path: std::path::PathBuf,
        arguments: Vec<String>,
        environment_variables: std::collections::HashMap<String, String>,
        working_directory: Option<String>,
        uid: Option<u32>,
        gid: Option<u32>,
        stdout_max: u32,
        stderr_max: u32,
    ) -> Result<Self> {
        if !path.is_file() {
            Err(Error(format!("'path' is not a file: {}.", path.display())))
        } else {
            Ok(Self {
                path,
                arguments,
                environment_variables,
                working_directory,
                uid,
                gid,
                stdout_max,
                stderr_max,
            })
        }
    }
}

impl TryFrom<&config::ProcessConfig> for ProcessConfig {
    type Error = Error;

    fn try_from(process: &config::ProcessConfig) -> std::result::Result<Self, Self::Error> {
        Self::new(
            process.path.clone(),
            process.arguments.clone(),
            process.environment_variables.clone(),
            process.working_directory.clone(),
            process.uid,
            process.gid,
            process.stdout_max,
            process.stderr_max,
        )
    }
}
