use std::{
    ffi::OsStr,
    fs,
    path::{Path, PathBuf},
    process::Command,
};

use tempfile::{tempdir, TempDir};

pub struct TestWorkspace {
    _tempdir: TempDir,
    root: PathBuf,
}

pub struct CommandOutput {
    pub status_code: i32,
    pub stdout: String,
    pub stderr: String,
}

impl TestWorkspace {
    pub fn new() -> Self {
        let tempdir = tempdir().expect("tempdir should exist");
        let root = tempdir.path().to_path_buf();
        Self {
            _tempdir: tempdir,
            root,
        }
    }

    pub fn root(&self) -> &Path {
        &self.root
    }

    #[allow(dead_code)]
    pub fn write_file(&self, relative_path: impl AsRef<Path>, contents: &str) {
        let path = self.root.join(relative_path);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).expect("parent directories should exist");
        }

        fs::write(path, contents).expect("file should be written");
    }

    #[allow(dead_code)]
    pub fn copy_fixture(&self, fixture_name: impl AsRef<Path>, destination: impl AsRef<Path>) {
        let source = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("tests")
            .join("fixtures")
            .join(fixture_name);
        let destination = self.root.join(destination);

        if let Some(parent) = destination.parent() {
            fs::create_dir_all(parent).expect("parent directories should exist");
        }

        fs::copy(source, destination).expect("fixture should be copied");
    }

    pub fn run_hen<I, S>(&self, args: I) -> CommandOutput
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        let output = Command::new(env!("CARGO_BIN_EXE_hen"))
            .args(args)
            .current_dir(self.root())
            .env("NO_COLOR", "1")
            .env("TERM", "dumb")
            .output()
            .expect("hen command should execute");

        CommandOutput {
            status_code: output.status.code().expect("process should exit normally"),
            stdout: String::from_utf8(output.stdout).expect("stdout should be utf-8"),
            stderr: String::from_utf8(output.stderr).expect("stderr should be utf-8"),
        }
    }
}
