use std::{
    fs,
    path::{Path, PathBuf},
};

use crate::{
    axvisor::board::{self, Board},
    context::{AxvisorCommandSnapshot, snapshot_path_value},
};

pub(crate) fn default_build_config_path(axvisor_dir: &Path) -> PathBuf {
    axvisor_dir.join(".build.toml")
}

pub(crate) fn available_board_names(axvisor_dir: &Path) -> anyhow::Result<Vec<String>> {
    board::board_names(axvisor_dir)
}

fn resolve_board(axvisor_dir: &Path, name: &str) -> anyhow::Result<Board> {
    board::find_board(axvisor_dir, name)?.ok_or_else(|| {
        let available = available_board_names(axvisor_dir).unwrap_or_default();
        anyhow!(
            "unknown Axvisor board `{name}` in {}; available boards: {}",
            board::board_dir(axvisor_dir).display(),
            available.join(", ")
        )
    })
}

pub(crate) fn write_defconfig(
    workspace_root: &Path,
    axvisor_dir: &Path,
    board_name: &str,
) -> anyhow::Result<PathBuf> {
    let board = resolve_board(axvisor_dir, board_name)?;
    let build_config_path = default_build_config_path(axvisor_dir);
    if let Some(parent) = build_config_path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::copy(&board.path, &build_config_path).map_err(|e| {
        anyhow!(
            "failed to copy board config {} to {}: {e}",
            board.path.display(),
            build_config_path.display()
        )
    })?;

    let mut snapshot = AxvisorCommandSnapshot::load(workspace_root)?;
    snapshot.config = Some(snapshot_path_value(workspace_root, &build_config_path));
    snapshot.store(workspace_root)?;

    Ok(build_config_path)
}

#[cfg(test)]
mod tests {
    use tempfile::tempdir;

    use super::*;
    use crate::context::{
        AxvisorQemuSnapshot, AxvisorUbootSnapshot, DEFAULT_AXVISOR_ARCH, DEFAULT_AXVISOR_TARGET,
    };

    fn write_board(root: &Path, name: &str, body: &str) -> PathBuf {
        let path = board::board_dir(root).join(format!("{name}.toml"));
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(&path, body).unwrap();
        path
    }

    #[test]
    fn write_defconfig_generates_build_toml_and_updates_snapshot() {
        let root = tempdir().unwrap();
        let axvisor_dir = root.path().join("os/axvisor");
        let source = write_board(
            &axvisor_dir,
            "roc-rk3568-pc",
            r#"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
target = "aarch64-unknown-none-softfloat"
features = ["fs", "rk3568-clk"]
log = "Info"
plat_dyn = true
vm_configs = []
"#,
        );
        let qemu_config = PathBuf::from("configs/qemu.toml");
        let existing_snapshot = AxvisorCommandSnapshot {
            arch: Some(DEFAULT_AXVISOR_ARCH.to_string()),
            target: Some(DEFAULT_AXVISOR_TARGET.to_string()),
            plat_dyn: Some(false),
            config: Some(PathBuf::from("os/axvisor/.build-aarch64.toml")),
            vmconfigs: vec![PathBuf::from("tmp/vm1.toml")],
            qemu: AxvisorQemuSnapshot {
                qemu_config: Some(qemu_config.clone()),
            },
            uboot: AxvisorUbootSnapshot {
                uboot_config: Some(PathBuf::from("configs/uboot.toml")),
            },
        };
        existing_snapshot.store(root.path()).unwrap();

        let path = write_defconfig(root.path(), &axvisor_dir, "roc-rk3568-pc").unwrap();

        assert_eq!(path, axvisor_dir.join(".build.toml"));
        assert_eq!(
            fs::read_to_string(&path).unwrap(),
            fs::read_to_string(source).unwrap()
        );

        let snapshot = AxvisorCommandSnapshot::load(root.path()).unwrap();
        assert_eq!(
            snapshot.config,
            Some(PathBuf::from("os/axvisor/.build.toml"))
        );
        assert_eq!(snapshot.arch, existing_snapshot.arch);
        assert_eq!(snapshot.target, existing_snapshot.target);
        assert_eq!(snapshot.plat_dyn, existing_snapshot.plat_dyn);
        assert_eq!(snapshot.vmconfigs, existing_snapshot.vmconfigs);
        assert_eq!(snapshot.qemu.qemu_config, Some(qemu_config));
    }

    #[test]
    fn available_board_names_match_filename_order() {
        let root = tempdir().unwrap();
        write_board(
            root.path(),
            "qemu-aarch64",
            r#"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
target = "aarch64-unknown-none-softfloat"
features = []
log = "Info"
plat_dyn = true
"#,
        );
        write_board(
            root.path(),
            "orangepi-5-plus",
            r#"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
target = "aarch64-unknown-none-softfloat"
features = ["rk3588-clk"]
log = "Info"
plat_dyn = true
"#,
        );

        assert_eq!(
            available_board_names(root.path()).unwrap(),
            vec!["orangepi-5-plus".to_string(), "qemu-aarch64".to_string()]
        );
    }

    #[test]
    fn resolve_board_config_reports_board_directory_for_unknown_name() {
        let root = tempdir().unwrap();
        write_board(
            root.path(),
            "qemu-aarch64",
            r#"
env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" }
target = "aarch64-unknown-none-softfloat"
features = []
log = "Info"
plat_dyn = true
"#,
        );

        let err = resolve_board(root.path(), "missing")
            .unwrap_err()
            .to_string();
        assert!(err.contains("unknown Axvisor board `missing`"));
        assert!(err.contains(&board::board_dir(root.path()).display().to_string()));
        assert!(err.contains("qemu-aarch64"));
    }
}
