//! Shell command pipeline steps (`command`, `command-if-missing`).

use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;

use super::super::expand::{expand_vars_with_settings, settings_env};
use super::super::spec::{PipelineStep, RigSpec};
use super::super::toolchain;
use crate::core::error::{Error, Result};

pub(in crate::core::rig) fn run_command_step(
    rig: &RigSpec,
    cmd: &str,
    cwd: Option<&str>,
    env: &HashMap<String, String>,
    settings: &[(String, String)],
) -> Result<()> {
    let expanded = expand_vars_with_settings(rig, cmd, settings);
    let mut command = Command::new(command_step_shell());
    command.arg("-c").arg(&expanded);

    if let Some(cwd) = cwd {
        let resolved = expand_vars_with_settings(rig, cwd, settings);
        command.current_dir(PathBuf::from(resolved));
    }

    if !env.contains_key("PATH") {
        if let Some(path) = toolchain::command_step_path() {
            command.env("PATH", path);
        }
    }

    for (k, v) in env {
        command.env(k, expand_vars_with_settings(rig, v, settings));
    }

    for (k, v) in settings_env(settings) {
        command.env(k, v);
    }

    let status = command.status().map_err(|e| {
        Error::rig_pipeline_failed(
            &rig.id,
            "command",
            format!("spawn failed for `{}`: {}", expanded, e),
        )
    })?;

    if !status.success() {
        let code = status.code().unwrap_or(-1);
        let hint = if code == 127 {
            ": command not found. Rig command steps bootstrap common toolchain PATHs automatically; if the tool lives elsewhere, set env.PATH for this step or prefer a typed build/git/check step"
        } else {
            ""
        };
        return Err(Error::rig_pipeline_failed(
            &rig.id,
            "command",
            format!("`{}` exited {}{}", expanded, code, hint),
        ));
    }
    Ok(())
}

pub(super) fn run_command_pipeline_step(
    rig: &RigSpec,
    step: &PipelineStep,
    settings: &[(String, String)],
) -> Result<()> {
    match step {
        PipelineStep::Command { cmd, cwd, env, .. } => {
            run_command_step(rig, cmd, cwd.as_deref(), env, settings)
        }
        PipelineStep::CommandIfMissing {
            missing,
            cmd,
            cwd,
            env,
            ..
        } => run_command_if_missing_step(rig, missing, cmd, cwd.as_deref(), env, settings),
        _ => unreachable!("command pipeline helper only accepts command steps"),
    }
}

fn run_command_if_missing_step(
    rig: &RigSpec,
    missing: &str,
    cmd: &str,
    cwd: Option<&str>,
    env: &HashMap<String, String>,
    settings: &[(String, String)],
) -> Result<()> {
    let expanded_missing = expand_vars_with_settings(rig, missing, settings);
    let missing_path = PathBuf::from(&expanded_missing);
    let resolved_missing = if missing_path.is_absolute() {
        missing_path
    } else if let Some(cwd) = cwd {
        PathBuf::from(expand_vars_with_settings(rig, cwd, settings)).join(missing_path)
    } else {
        missing_path
    };

    if resolved_missing.exists() {
        return Ok(());
    }

    run_command_step(rig, cmd, cwd, env, settings)
}

#[cfg(unix)]
fn command_step_shell() -> &'static str {
    "/bin/sh"
}

#[cfg(not(unix))]
fn command_step_shell() -> &'static str {
    "sh"
}

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

    #[test]
    fn settings_env_adds_shell_safe_dotted_keys() {
        let env = settings_env(&[(
            "components.woocommerce.extensions.wordpress.selected_runtime_source_root".to_string(),
            "/workspace/source".to_string(),
        )]);

        assert!(env.iter().any(|(key, value)| {
            key == "HOMEBOY_SETTINGS_COMPONENTS_WOOCOMMERCE_EXTENSIONS_WORDPRESS_SELECTED_RUNTIME_SOURCE_ROOT"
                && value == "/workspace/source"
        }));
    }
}
