use std::env;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;

fn main() {
    // Directory with DTS fixtures
    let dts_dir = PathBuf::from("test/dts");
    println!("cargo:rerun-if-changed=test/dts");
    // check the environment
    println!("cargo:rerun-if-changed=build.rs");
    println!("--- check the environment");
    if !command_installed("git") {
        panic!("this test requires git");
    }
    if !command_installed("dtc") {
        panic!("this test requires the device tree compiler(dtc)");
    }
    println!("Creating test folder...");
    let _ = fs::create_dir("test");
    println!("Checking file...");
    let dtb_path = Path::new("test/test.dtb");
    if !dtb_path.is_file() {
        let dts_path = Path::new("test/test.dts");
        if !dts_path.is_file() {
            println!(
                "Downloading test.dts from https://gist.github.com/072176edd54cd207c1d800c25d384cd2.git"
            );

            let download_dir = Path::new("test/.download");
            if download_dir.exists()
                && let Err(err) = fs::remove_dir_all(download_dir)
            {
                panic!("failed to clear temporary download dir: {}", err);
            }

            let status = Command::new("git")
                .arg("clone")
                .arg("https://gist.github.com/072176edd54cd207c1d800c25d384cd2.git")
                .arg(download_dir)
                .stdout(Stdio::null())
                .stdin(Stdio::null())
                .status();

            match status {
                Ok(s) if s.success() => {
                    let downloaded_dts = download_dir.join("test.dts");
                    if !downloaded_dts.is_file() {
                        panic!("downloaded repository did not contain test.dts");
                    }
                    if let Err(err) = fs::copy(&downloaded_dts, dts_path) {
                        panic!("failed to copy downloaded test.dts: {}", err);
                    }
                }
                Ok(s) => {
                    panic!(
                        "git clone exited with status {}, failed to fetch test.dts",
                        s
                    );
                }
                Err(err) => {
                    panic!("failed to run git clone: {}", err);
                }
            }

            if let Err(err) = fs::remove_dir_all(download_dir) {
                println!("warning: failed to remove temporary download dir: {}", err);
            }
        }

        println!("Compiling test.dts file to test.dtb...");
        let status = Command::new("dtc")
            .arg("-I")
            .arg("dts")
            .arg("-O")
            .arg("dtb")
            .arg("-o")
            .arg("test/test.dtb")
            .arg(dts_path)
            .stdout(Stdio::null())
            .stdin(Stdio::null())
            .status();

        match status {
            Ok(s) if s.success() => {}
            Ok(s) => panic!("dtc failed with status {} while compiling test/test.dts", s),
            Err(err) => panic!("failed to run dtc: {}", err),
        }
    }

    // OUT_DIR is provided by Cargo
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

    // Find all .dts files
    let entries = fs::read_dir(&dts_dir).unwrap();
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|s| s.to_str()) != Some("dts") {
            continue;
        }
        let file_name = path.file_stem().unwrap().to_string_lossy().to_string();
        let out_path = out_dir.join(format!("{}.dtb", file_name));

        // Invalidate when source changes
        println!("cargo:rerun-if-changed={}", path.display());

        // Try to run dtc to build the dtb
        let status = Command::new("dtc")
            .args(["-O", "dtb", "-o"])
            .arg(&out_path)
            .arg(&path)
            .status();

        match status {
            Ok(s) if s.success() => {
                // Success
            }
            Ok(s) => {
                // dtc returned error; fail the build so tests don't silently skip
                panic!(
                    "dtc failed (exit: {}), cannot build {}",
                    s,
                    out_path.display()
                );
            }
            Err(e) => {
                // dtc not present or failed to spawn; fail the build as requested
                panic!(
                    "failed to run dtc: {}. Required to build {}",
                    e,
                    out_path.display()
                );
            }
        }
    }
}

fn command_installed(command_name: &str) -> bool {
    Command::new(command_name)
        .arg("--version")
        .stdout(Stdio::null())
        .stdin(Stdio::null())
        .status()
        .is_ok_and(|status| status.success())
}