use std::{env, path::Path, process::Command};
use sysinfo::{CpuExt, CpuRefreshKind, RefreshKind, System, SystemExt};
use tera::{Context, Tera};

fn main() {
    built::write_built_file().expect("built failed");
    gen_ch4_version();
    export_sysinfo();
    write_main();
}

fn export_sysinfo() {
    let na = Some("n/a".to_string());
    let mut name = na.clone();
    let mut os_version = na.clone();
    let mut cpu_vendor = na.clone();
    let mut cpu_brand = na.clone();

    if System::IS_SUPPORTED {
        let system = System::new_with_specifics(RefreshKind::new().with_cpu(CpuRefreshKind::new()));
        name = system.name().or_else(|| na.clone());
        os_version = system.long_os_version().or_else(|| na.clone());
        let cpu = system.cpus().first();
        cpu_vendor = cpu
            .map(|cpu| cpu.vendor_id().to_string())
            .or_else(|| na.clone());
        cpu_brand = cpu
            .map(|cpu| cpu.brand().to_string())
            .or_else(|| na.clone());
    }

    println!("cargo:rustc-env=CH4_SYSINFO_NAME={}", name.unwrap());
    println!(
        "cargo:rustc-env=CH4_SYSINFO_OS_VERSION={}",
        os_version.unwrap()
    );
    println!(
        "cargo:rustc-env=CH4_SYSINFO_CPU_VENDOR={}",
        cpu_vendor.unwrap()
    );
    println!(
        "cargo:rustc-env=CH4_SYSINFO_CPU_BRAND={}",
        cpu_brand.unwrap()
    );
}

fn gen_ch4_version() {
    let mut ch4_version = env::var("CARGO_PKG_VERSION").unwrap();

    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let path = Path::new(&manifest_dir);
    match built::util::get_repo_head(path) {
        Ok(Some((_, _, short_hash))) => {
            ch4_version = format!("{ch4_version} git:{short_hash}");
        }
        Ok(None) => {}
        Err(_) => {}
    }

    println!("cargo:rustc-env=CH4_VERSION={ch4_version}");
}

fn format_file(path: &std::path::Path) {
    let path_str = path.to_str().unwrap();
    let output = Command::new("rustfmt")
        .args(["--edition", "2018"])
        .arg(path_str)
        .output()
        .expect("failed to launch rustfmt");

    assert!(
        output.status.success(),
        "failed to format {}\nstdout: {}\nstderr: {}",
        path_str,
        std::str::from_utf8(&output.stdout).unwrap(),
        std::str::from_utf8(&output.stderr).unwrap(),
    );
}

fn write_file(tera: &Tera, context: &Context, file_name: &str) {
    let out_dir = std::env::var_os("OUT_DIR").unwrap();
    let file_data = tera
        .render("main_template.rs", context)
        .expect("failed to render template");
    let file_path = std::path::Path::new(&out_dir).join(file_name);
    std::fs::write(&file_path, file_data).expect("failed to write file");
    format_file(&file_path);
}

fn write_main() {
    let tera = match Tera::new("templates/*.rs") {
        Ok(t) => t,
        Err(e) => {
            panic!("Tera parsing error(s): {e}");
        }
    };

    for async_value in &["true", "false"] {
        let mut context = Context::new();
        context.insert("async", async_value);
        let file_name = format!(
            "{}_main.rs",
            if *async_value == "true" {
                "async"
            } else {
                "std"
            }
        );
        write_file(&tera, &context, &file_name);
    }
}