pub mod file_io;
pub mod moose;
use anyhow::Result;

/**
 * A module to enable us to use the agent-cli as a means to interact with the user's
 * local terminal based programs.
 */
use std::process::Command;

use crate::question_and_answers::Question;
use crate::survey::utils::AppSpinner;

/// Executes a shell command and returns the output as a Result string
///
/// If the command fails, the error is returned as a Result string
pub fn execute_command(command: &str, args: Option<&[&str]>) -> Result<String> {
    let shell = std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string());

    let full_command = match args {
        Some(args) => {
            let quoted_args: Vec<String> = args
                .iter()
                .map(|&arg| {
                    if arg.contains(' ') || arg.contains('*') {
                        format!("\"{}\"", arg)
                    } else {
                        arg.to_string()
                    }
                })
                .collect();
            format!("{} {}", command, quoted_args.join(" "))
        }
        None => command.to_string(),
    };

    // Confirm that the user wants to run the command
    Question::confirm(&format!("Run command: {}", full_command))
        .with_confirm_default(true)
        .with_help(&format!(
            "Double check that this is indeed what you want to run. Running arbitrary commands can result in unexpected behavior.",
        ))
        .get_answer()?;

    let mut spinner = AppSpinner::new(&format!("Running command: {}", &full_command));
    let output = Command::new(shell).arg("-c").arg(&full_command).output()?;

    if output.status.success() {
        spinner.stop_with_success_message(&format!("Successfully ran command: {}", full_command));
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        spinner.stop_with_error_message("Command failed!");
        Err(anyhow::anyhow!(format!(
            "Command {} failed | stdout: {}, stderr: {}",
            // Combine command and args. Make sure to include quotes around the args if they are present
            if let Some(args) = args {
                format!("{} {}", command, args.join(" "))
            } else {
                command.to_string()
            },
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        )))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_execute_valid_command() {
        let result = execute_command("echo", Some(&["hello"]));
        assert!(result.is_ok());
        assert_eq!(result.unwrap().trim(), "hello");
    }

    #[test]
    fn test_execute_invalid_command() {
        let result = execute_command("nonexistentcommand", None);
        assert!(result.is_err());
    }

    #[test]
    fn test_execute_command_with_args() {
        let result = execute_command("ls", Some(&["~"]));
        println!("Result: {:?}", result);
        assert!(result.is_ok());
        assert!(result.unwrap().contains("Documents"));
    }
}
