// use std::{collections::HashMap, path::Path};

use std::{
    collections::HashMap, fs::OpenOptions, os::unix::fs::OpenOptionsExt, path::Path,
    process::Command,
};

use crate::{
    env::BuildEnv,
    error::Error,
    util::{self, git_clone},
};

const TOOLCHAIN_URL: &str = "https://git.alnyan.me/yggdrasil/yggdrasil-rust";

#[derive(Debug, serde::Serialize)]
struct RustBuildConfig {
    host: Vec<String>,
    target: Vec<String>,
}

#[derive(Debug, serde::Serialize)]
struct RustTargetConfig {
    cc: Option<String>,
    cxx: Option<String>,
}

#[derive(Debug, serde::Serialize)]
struct RustRustConfig {
    incremental: bool,
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
struct RustToolchainConfig {
    profile: String,

    build: RustBuildConfig,
    target: HashMap<String, RustTargetConfig>,
    rust: RustRustConfig,
}

fn configure<P: AsRef<Path>>(env: &BuildEnv, config_toml_path: P) -> Result<(), Error> {
    let config = RustToolchainConfig {
        profile: "user".into(),

        build: RustBuildConfig {
            host: vec![env.host_triple.clone()],
            target: vec![
                env.host_triple.clone(),
                "x86_64-unknown-yggdrasil".into(),
                "riscv64-unknown-yggdrasil".into(),
                "aarch64-unknown-yggdrasil".into(),
            ],
        },
        target: HashMap::from_iter([
            (
                "aarch64-unknown-yggdrasil".into(),
                RustTargetConfig {
                    cc: Some("aarch64-linux-gnu-gcc".into()),
                    cxx: Some("aarch64-linux-gnu-g++".into()),
                },
            ),
            (
                "riscv64-unknown-yggdrasil".into(),
                RustTargetConfig {
                    cc: Some("clang".into()),
                    cxx: Some("clang++".into()),
                },
            ),
        ]),
        rust: RustRustConfig { incremental: true },
    };

    let toml = toml::to_string_pretty(&config)?;
    std::fs::write(config_toml_path, toml.as_bytes())?;

    Ok(())
}

pub fn is_toolchain_linked() -> bool {
    util::run_external_command("cargo", ["+ygg-stage1", "--version"], true).is_ok()
}

fn link_rustup_toolchain<P: AsRef<Path>>(env: &BuildEnv, toolchain_root: P) -> Result<(), Error> {
    util::run_external_command(
        "rustup",
        [
            "toolchain",
            "link",
            "ygg-stage1",
            &format!(
                "{}",
                toolchain_root
                    .as_ref()
                    .join("build")
                    .join(&env.host_triple)
                    .join("stage1")
                    .display()
            ),
        ],
        false,
    )
}

fn do_build<P: AsRef<Path>>(toolchain_root: P) -> Result<(), Error> {
    let status = Command::new("./x")
        .current_dir(toolchain_root)
        .args(["build", "--stage=1"])
        .status()?;
    if status.success() {
        Ok(())
    } else {
        Err(Error::ToolchainBuildFailed)
    }
}

fn install_shims<P: AsRef<Path>>(env: &BuildEnv, toolchain_root: P) -> Result<(), Error> {
    use std::io::Write;

    let toolchain_root = toolchain_root.as_ref();
    let build_dir = toolchain_root.join("build").join(&env.host_triple);
    let stage1_rustlib = build_dir
        .join("stage1/lib/rustlib/")
        .join(&env.host_triple)
        .join("lib");
    let stage1_tools_bin = build_dir.join("stage1-tools-bin");
    let stage1_bin = build_dir.join("stage1/bin");

    let rust_analyzer = stage1_bin.join("rust-analyzer");

    let mut file = OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .mode(0o755)
        .open(rust_analyzer)?;

    writeln!(file, "#!/bin/sh")?;
    writeln!(
        file,
        "LD_LIBRARY_PATH={} {}/rust-analyzer",
        stage1_rustlib.display(),
        stage1_tools_bin.display()
    )?;

    Ok(())
}

pub fn fetch(env: &BuildEnv, branch: &str) -> Result<(), Error> {
    let path = env.workspace_root.join("toolchain");
    git_clone(path, TOOLCHAIN_URL, branch, false)
}

pub fn build(env: &BuildEnv) -> Result<(), Error> {
    let toolchain_path = env.workspace_root.join("toolchain");
    let config_toml_path = toolchain_path.join("config.toml");

    assert!(toolchain_path.exists());

    // Write config.toml
    if !config_toml_path.exists() {
        configure(env, config_toml_path)?;
    }

    log::info!("Building toolchain");
    do_build(&toolchain_path)?;
    log::info!("Installing rust tool shims");
    install_shims(env, &toolchain_path)?;

    if !is_toolchain_linked() {
        log::info!("Linking the newly built toolchain to +ygg-stage1");
        link_rustup_toolchain(env, &toolchain_path)?;
    } else {
        log::debug!("Toolchain already linked");
    }

    Ok(())
}
