Documentation
use base64::prelude::*;
use std::{
    process::{Command, Stdio},
    rc::Rc,
    str::FromStr,
};

use indexmap::IndexMap;

use crate::{
    functions_definitions::{Arguments, Example, FunctionDefinitions},
    json_value::JsonValue,
    processor::Context,
    selection::Get,
};

pub fn get() -> FunctionDefinitions {
    FunctionDefinitions::new("exec", 1, usize::MAX, |vec| {
        struct Impl(Vec<Rc<dyn Get>>);
        impl Get for Impl {
            fn get(&self, context: &Context) -> Option<JsonValue> {
                let Some(JsonValue::String(command)) = self.0.apply(context, 0) else {
                    return None;
                };
                let mut command = Command::new(command);
                for arg in self.0.iter().skip(1) {
                    let Some(JsonValue::String(arg)) = arg.get(context) else {
                        return None;
                    };
                    command.arg(arg);
                }
                command.stdout(Stdio::piped());
                command.stderr(Stdio::piped());
                let Ok(child) = command.spawn() else {
                    return None;
                };
                let Ok(result) = child.wait_with_output() else {
                    return None;
                };
                let mut value = IndexMap::with_capacity(6);
                value.insert("success".into(), result.status.success().into());
                if let Some(code) = result.status.code() {
                    let code = f64::from(code);
                    value.insert("exit_code".into(), code.into());
                }
                value.insert(
                    "raw_stdout".into(),
                    BASE64_STANDARD.encode(&result.stdout).into(),
                );
                if let Ok(stdout) = String::from_utf8(result.stdout) {
                    value.insert("stdout".into(), stdout.into());
                }
                value.insert(
                    "raw_stderr".into(),
                    BASE64_STANDARD.encode(&result.stderr).into(),
                );
                if let Ok(stderr) = String::from_utf8(result.stderr) {
                    value.insert("stderr".into(), stderr.into());
                }
                Some(value.into())
            }
        }
        Rc::new(Impl(vec))
    })
        .add_alias("execute")
        .add_description_line("Execute an external process and wait for it's completion.")
        .add_description_line(
            "If all the arguments are strings run a process with that list."
        )
        .add_description_line("The result is an object with:")
        .add_description_line(
            "* `success` Boolean to indicate if the process was successful."
        )
        .add_description_line("* `exit_code` The process exit code.")
        .add_description_line(
            "* `raw_stdout` The standard output of the process encode as BASE64."
        )
        .add_description_line("* `stdout` The standard output as text.")
        .add_description_line(
            "* `raw_stderr` The standard error of the process encode as BASE64."
        )
        .add_description_line("* `stderr` The standard error as text.")
        .add_example(
            Example::new()
                .add_argument("\"echo\"")
                .add_argument("\"hello\"")
                .add_argument("\"world\"")
                .validate_output(|o| {
                    if cfg!(windows) {
                        o.is_none()
                    } else {
                        let expected =
                            JsonValue::from_str(r#"{"success": true, "exit_code": 0, "raw_stdout": "aGVsbG8gd29ybGQK", "stdout": "hello world\n", "raw_stderr": "", "stderr": ""}"#).ok();
                        expected == *o
                    }
                })
        )
        .add_example(
            Example::new()
                .add_argument("\"cat\"")
                .add_argument("\"no such file\"")
                .validate_output(|o| {
                    if cfg!(windows) {
                        o.is_none()
                    } else {
                        let expected = JsonValue::from_str(r#"{"success": false, "exit_code": 1, "raw_stdout": "", "stdout": "", "raw_stderr": "Y2F0OiAnbm8gc3VjaCBmaWxlJzogTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeQo=", "stderr": "cat: 'no such file': No such file or directory\n"}"#).ok();
                        expected == *o
                    }
                })
        )
        .add_example(Example::new().add_argument("\"no such exec\""))
        .add_example(Example::new().add_argument("23").explain("23 is not a string"))
        .add_example(
            Example::new()
                .add_argument("\"echo\"")
                .add_argument("23")
                .explain("23 is not a string")
        )
}