use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

pub(crate) type AnyError = Box<dyn Error>;
pub(crate) type Result<T> = std::result::Result<T, AnyError>;

pub(crate) fn usage_error<T>(message: String) -> Result<T> {
    Err(message.into())
}

pub(crate) fn repo_root() -> PathBuf {
    if let Ok(root) = env::var("EXECUTESOFT_ROOT") {
        return PathBuf::from(root);
    }
    let mut dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    loop {
        if dir.join("contracts/protobuf").exists() && dir.join("services").exists() {
            return dir;
        }
        if !dir.pop() {
            return PathBuf::from(".");
        }
    }
}

pub(crate) fn repo_path(path: &str) -> PathBuf {
    let raw = PathBuf::from(path);
    if raw.is_absolute() || raw.exists() {
        return raw;
    }
    let from_root = repo_root().join(path);
    if from_root.exists() {
        return from_root;
    }
    raw
}

pub(crate) fn run_cmd(dir: &Path, program: &str, args: &[String]) -> Result<()> {
    let status = Command::new(program)
        .args(args)
        .current_dir(dir)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()?;
    if status.success() {
        Ok(())
    } else {
        Err(format!("{program} exited with {status}").into())
    }
}

pub(crate) fn run_make(dir: &Path, target: &str, vars: &[String]) -> Result<()> {
    let mut args = vec![target.to_string()];
    args.extend(vars.iter().cloned());
    run_cmd(dir, "make", &args)
}

pub(crate) fn make_vars(args: &[String]) -> Vec<String> {
    let mut vars = Vec::new();
    for arg in args {
        if let Some(raw) = arg.strip_prefix("--") {
            if let Some((key, value)) = raw.split_once('=') {
                vars.push(format!(
                    "{}={}",
                    key.replace('-', "_").to_uppercase(),
                    value
                ));
            }
        } else if arg.contains('=') {
            vars.push(arg.clone());
        }
    }
    vars
}
