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

use crate::{
    env::{BuildEnv, Profile},
    error::Error,
};

// #[derive(Debug, Clone, Deserialize)]
// pub struct ModuleDependency {
//     pub version: Option<semver::VersionReq>,
//     pub path: PathBuf,
// }

// #[derive(Debug, Deserialize)]
// pub struct ModuleManifest {
//     pub name: String,
//     pub version: semver::Version,
//     #[serde(default)]
//     pub dependencies: HashMap<String, ModuleDependency>,
// }

// #[derive(Debug)]
// pub struct ModuleInfo {
//     pub dependencies: Vec<(String, String, ModuleDependency)>,
//     pub module_dir_name: PathBuf,
//     pub manifest_dir: PathBuf,
//     pub manifest: ModuleManifest,
// }

pub enum CargoBuilder<'e> {
    Host(bool),
    Userspace(&'e BuildEnv),
    Ygglibc(&'e BuildEnv),
    Kernel(&'e BuildEnv),
    // Module(&'e BuildEnv, &'e ModuleInfo),
}

impl<'e> CargoBuilder<'e> {
    fn make_command<P: AsRef<Path>, S: AsRef<OsStr>>(
        self,
        dir: P,
        arg: S,
    ) -> Result<Command, Error> {
        let arg = arg.as_ref();
        let mut command = Command::new("cargo");

        command.current_dir(dir);

        match self {
            Self::Userspace(env) => {
                // Add clippy dependency libraries to LD_LIBRARY_PATH
                let toolchain_libs_path = env
                    .workspace_root
                    .join("toolchain/build")
                    .join(&env.host_triple)
                    .join("stage1/lib/rustlib")
                    .join(&env.host_triple)
                    .join("lib");
                let ld_library_path = std::env::var("LD_LIBRARY_PATH");
                let ld_library_path = match ld_library_path {
                    Ok(path) => format!("{}:{}", toolchain_libs_path.display(), path),
                    Err(_) => toolchain_libs_path.display().to_string(),
                };

                command.env("LD_LIBRARY_PATH", ld_library_path);

                command
                    .arg("+ygg-stage1")
                    .arg(arg)
                    .arg(&format!("--target={}", env.arch.user_triple()));

                if env.verbose {
                    command.arg("-v");
                }

                if env.profile == Profile::Release {
                    command.arg("--release");
                }
            }
            Self::Ygglibc(env) => {
                let target = format!(
                    "{}/{:?}-unknown-none.json",
                    env.workspace_root
                        .join("userspace/lib/ygglibc/etc")
                        .display(),
                    env.arch
                );
                let build_std_features =
                    "compiler-builtins-mangled-names,compiler-builtins-mem".to_owned();

                command
                    .arg(arg)
                    .arg("-Zbuild-std=core,alloc")
                    .arg(&format!("-Zbuild-std-features={build_std_features}"))
                    .arg(&format!("--target={target}"));

                if env.verbose {
                    command.arg("-v");
                }

                if env.profile == Profile::Release {
                    command.arg("--release");
                }
            }
            Self::Host(verbose) => {
                command.arg("+nightly").arg(arg);

                if verbose {
                    command.arg("-v");
                }
            }
            Self::Kernel(env) => {
                command.env(
                    "RUSTFLAGS",
                    format!(
                        "-C link-arg=-T{}/etc/ld/{}.ld",
                        env.workspace_root.display(),
                        env.kernel_triple
                    ),
                );
                command.arg("+nightly").arg(arg);

                if env.verbose {
                    command.arg("-v");
                }

                command.arg(format!(
                    "--target={}/etc/{}.json",
                    env.workspace_root.display(),
                    env.kernel_triple
                ));

                command.args([
                    "-Z",
                    "build-std=core,alloc,compiler_builtins",
                    "-Zunstable-options",
                ]);

                if env.profile == Profile::Release {
                    command.arg("--release");
                }
            } // Self::Module(env, module) => {
              //     let mut lib_paths = vec![];
              //     let mut extern_libs = vec![];
              //     let mut dependency_list = String::new();

              //     for (i, (name, dep)) in module.manifest.dependencies.iter().enumerate() {
              //         if i != 0 {
              //             dependency_list.push(':');
              //         }

              //         let dep_path = module.manifest_dir.join(&dep.path).canonicalize()?;
              //         let output_dir = dep_path.join(format!(
              //             "target/{}/{}",
              //             env.kernel_triple,
              //             env.profile.name()
              //         ));
              //         let dependency_so = output_dir.join(format!("lib{}.so", name));

              //         lib_paths.push(output_dir.join("deps"));
              //         lib_paths.push(output_dir.clone());
              //         lib_paths.push(dep_path.join(format!("target/{}/deps", env.profile.name())));

              //         dependency_list.push_str(&format!("{}={}", name, dependency_so.display()));

              //         extern_libs.push((name.clone(), dependency_so));
              //     }

              //     lib_paths.push(env.kernel_output_dir.join("deps"));
              //     lib_paths.push(env.kernel_output_dir.clone());
              //     lib_paths.push(
              //         env.workspace_root
              //             .join(format!("target/{}/deps", env.profile.name())),
              //     );
              //     extern_libs.push(("libk".into(), env.kernel_output_dir.join("liblibk.so")));

              //     let mut rust_flags = Vec::new();
              //     for lib_path in lib_paths {
              //         rust_flags.push(format!("-L{}", lib_path.display()));
              //     }
              //     for (lib, path) in extern_libs {
              //         rust_flags.push(format!("--extern {}={}", lib, path.display()));
              //     }
              //     rust_flags.push("-Cprefer-dynamic".into());

              //     command.env("RUSTFLAGS", rust_flags.join(" "));
              //     command.env("MOD_DEPENDENCIES", dependency_list);
              //     command.env("MOD_LIBK_SO", env.kernel_output_dir.join("liblibk.so"));

              //     command.arg("+nightly").arg(arg);

              //     if env.verbose {
              //         command.arg("-v");
              //     }

              //     let target_spec_arg = format!(
              //         "--target={}/etc/{}.json",
              //         env.workspace_root.display(),
              //         env.kernel_triple
              //     );

              //     command.arg(target_spec_arg);

              //     if env.profile == Profile::Release {
              //         command.arg("--release");
              //     }
              // }
        }

        Ok(command)
    }

    pub fn run<P: AsRef<Path>, S: AsRef<OsStr>>(self, dir: P, arg: S) -> Result<(), Error> {
        let mut command = self.make_command(dir, arg)?;

        log::trace!("Run cargo: {:?}", command);

        let status = command.status()?;
        if status.success() {
            Ok(())
        } else {
            Err(Error::CargoFailed)
        }
    }

    pub fn build<P: AsRef<Path>>(self, dir: P) -> Result<(), Error> {
        self.run(dir, "build")
    }
}
