use anyhow::{Context, Result};
use handlebars::Handlebars;

use std::path::Path;
use std::process::Child;
use std::process::Command;

use crate::filesystem::{Filesystem, RealFilesystem};

pub(crate) fn run_hook(
    location: &Path,
    cache_dir: &Path,
    handlebars: &Handlebars<'_>,
    variables: &crate::config::Variables,
) -> Result<()> {
    let bat_sibling = if cfg!(windows) && location.extension().is_some_and(|e| e == "sh") {
        let candidate = location.with_extension("bat");
        if candidate.exists() {
            debug!("Using hook file {:?} on windows", candidate);
            Some(candidate)
        } else {
            None
        }
    } else {
        None
    };
    let location: &Path = bat_sibling.as_deref().unwrap_or(location);

    if !location.exists() {
        debug!("Hook file at {:?} missing", location);
        return Ok(());
    }

    let mut script_file = cache_dir.join(location);
    if cfg!(windows) {
        script_file.set_extension("bat");
    }

    debug!("Rendering script {:?} -> {:?}", location, script_file);
    let mut fs = RealFilesystem::new(false);
    crate::actions::perform_template_deploy(
        location,
        &script_file,
        None,
        &mut fs,
        handlebars,
        variables,
    )
    .context("deploy script")?;
    fs.copy_permissions(location, &script_file, &None)
        .context("copy permissions from source to cache")?;

    debug!("Running script file");
    let mut child = run_script_file(&script_file)?;

    anyhow::ensure!(
        child.wait().context("wait for child shell")?.success(),
        "subshell returned error"
    );

    Ok(())
}

#[cfg(unix)]
fn run_script_file(script: &Path) -> Result<Child> {
    use std::os::unix::fs::PermissionsExt;

    let permissions = script.metadata()?.permissions();
    if !script.is_dir() && permissions.mode() & 0o111 != 0 {
        Command::new(script).spawn().context("spawn script file")
    } else {
        Command::new("sh")
            .arg(script)
            .spawn()
            .context("spawn shell")
    }
}

#[cfg(windows)]
fn run_script_file(script: &Path) -> Result<Child> {
    Command::new(script).spawn().context("spawn batch file")
}
