use std::io::{self, Write};
use std::process::{Command, Stdio};

use owo_colors::OwoColorize;

use crate::backends::{Error, Result};
use crate::{mod_err, nest_errors};

#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum Perms {
    Root,
    User,
}

pub fn run_command_for_stdout<I>(args: I, perms: Perms, hide_stderr: bool) -> Result<String>
where
    I: IntoIterator,
    I::Item: Into<String>,
{
    let args = get_command(args, perms)?;

    let (first_arg, remaining_args) = args.split_first().unwrap();

    let mut command = Command::new(first_arg);
    let output = command
        .args(remaining_args)
        .stdin(Stdio::inherit())
        .stdout(Stdio::piped())
        .stderr(if !hide_stderr {
            Stdio::inherit()
        } else {
            Stdio::null()
        })
        .output()
        .map_err(|e| {
            let msg = format!("Failed to run command {}", cmd_string(&args));
            nest_errors!(msg, e)
        })?;

    if output.status.success() {
        let output = String::from_utf8(output.stdout).map_err(|_| {
            let msg = format!("Command produced invalid utf8: {}", cmd_string(&args));
            mod_err!(msg)
        })?;
        Ok(output)
    } else {
        let msg = format!("Command failed: {}", cmd_string(&args));
        Err(mod_err!(msg))
    }
}

pub fn run_command<I>(args: I, perms: Perms) -> Result<()>
where
    I: IntoIterator,
    I::Item: Into<String>,
{
    let args = get_command(args, perms)?;

    let (first_arg, remaining_args) = args.split_first().unwrap();

    let mut command = Command::new(first_arg);
    let status = command
        .args(remaining_args)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()
        .map_err(|e| {
            let msg = format!("Failed to run command: {}", cmd_string(&args));
            nest_errors!(msg, e)
        })?;

    if status.success() {
        Ok(())
    } else {
        let msg = format!("command failed: {}", cmd_string(&args));
        Err(mod_err!(msg))
    }
}

#[allow(clippy::print_stderr)]
pub fn dry_run_command<I>(args: I, perms: Perms) -> Result<()>
where
    I: IntoIterator,
    I::Item: Into<String>,
{
    let command = get_command(args, perms)?;
    eprintln!("{} {}", ">".green(), command.join(" "));
    Ok(())
}

#[allow(clippy::print_stdout)]
pub fn confirmation_prompt<P, I>(prompt: P, items: I) -> Result<bool>
where
    P: AsRef<str>,
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    std::println!("{}", prompt.as_ref());

    let help_string = items
        .into_iter()
        .fold(" ".to_owned(), |acc, elem| acc + elem.as_ref() + " ");

    std::println!("{}{}{}", '['.cyan(), help_string.cyan(), ']'.cyan());
    std::print!("[{}/{}]: ", 'Y'.green(), 'n'.red());
    io::stdout().flush().expect("Failed to flush the output");

    let mut answer = String::new();
    io::stdin()
        .read_line(&mut answer)
        .map_err(|e| nest_errors!("Failed to read response", e))?;

    let answer = answer.to_lowercase();
    let answer = answer.trim();

    if answer == "y" || answer == "yes" || answer.is_empty() {
        Ok(true)
    } else if answer == "n" || answer == "no" {
        Ok(false)
    } else {
        Err(mod_err!("Failed to retrieve answer"))
    }
}

fn get_command<I>(args: I, perms: Perms) -> Result<Vec<String>>
where
    I: IntoIterator,
    I::Item: Into<String>,
{
    let args: Vec<String> = args.into_iter().map(Into::into).collect();

    if args.is_empty() {
        panic!("Cannot run an empty command");
    }

    let command = Some("sudo".to_string())
        .filter(|_| perms == Perms::Root)
        .into_iter()
        .chain(args)
        .collect();

    Ok(command)
}

fn cmd_string<I>(args: I) -> String
where
    I: IntoIterator,
    I::Item: Into<String>,
{
    args.into_iter()
        .fold("".to_owned(), |acc, elem| acc + " " + &elem.into())
}
