use std::path::{Path, PathBuf};
#[cfg(test)]
use std::sync::{Mutex, OnceLock};

/// Environment variable names used by the KVM backend.
pub const ENV_QEMU_PATH: &str = "PLASMAVMC_QEMU_PATH";
pub const ENV_QCOW2_PATH: &str = "PLASMAVMC_QCOW2_PATH";
pub const ENV_KERNEL_PATH: &str = "PLASMAVMC_KERNEL_PATH";
pub const ENV_INITRD_PATH: &str = "PLASMAVMC_INITRD_PATH";
pub const ENV_RUNTIME_DIR: &str = "PLASMAVMC_RUNTIME_DIR";
pub const ENV_QMP_TIMEOUT_SECS: &str = "PLASMAVMC_QMP_TIMEOUT_SECS";
pub const ENV_NBD_MAX_QUEUES: &str = "PLASMAVMC_NBD_MAX_QUEUES";

/// Resolve QEMU binary path, falling back to a provided default.
pub fn resolve_qemu_path(default: impl AsRef<Path>) -> PathBuf {
    std::env::var_os(ENV_QEMU_PATH)
        .map(PathBuf::from)
        .unwrap_or_else(|| default.as_ref().to_path_buf())
}

/// Resolve optional QCOW2 image path from env.
pub fn resolve_qcow2_path() -> Option<PathBuf> {
    std::env::var_os(ENV_QCOW2_PATH).map(PathBuf::from)
}

/// Resolve optional kernel/initrd paths from env.
pub fn resolve_kernel_initrd() -> (Option<PathBuf>, Option<PathBuf>) {
    let kernel = std::env::var_os(ENV_KERNEL_PATH).map(PathBuf::from);
    let initrd = std::env::var_os(ENV_INITRD_PATH).map(PathBuf::from);
    (kernel, initrd)
}

/// Resolve the runtime directory used for QMP sockets and console logs.
pub fn resolve_runtime_dir() -> PathBuf {
    std::env::var_os(ENV_RUNTIME_DIR)
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("/run/libvirt/plasmavmc"))
}

/// Resolve the QMP readiness timeout used during VM lifecycle operations.
pub fn resolve_qmp_timeout_secs() -> u64 {
    std::env::var(ENV_QMP_TIMEOUT_SECS)
        .ok()
        .and_then(|value| value.parse::<u64>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(15)
}

pub fn resolve_nbd_max_queues() -> u16 {
    std::env::var(ENV_NBD_MAX_QUEUES)
        .ok()
        .and_then(|value| value.parse::<u16>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(16)
}

#[cfg(test)]
pub(crate) fn env_test_lock() -> &'static Mutex<()> {
    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
    LOCK.get_or_init(|| Mutex::new(()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn resolve_qemu_default_when_unset() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::remove_var(ENV_QEMU_PATH);
        let path = resolve_qemu_path("/usr/bin/qemu-system-x86_64");
        assert_eq!(path, PathBuf::from("/usr/bin/qemu-system-x86_64"));
    }

    #[test]
    fn resolve_qemu_from_env() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::set_var(ENV_QEMU_PATH, "/tmp/qemu");
        let path = resolve_qemu_path("/usr/bin/qemu-system-x86_64");
        assert_eq!(path, PathBuf::from("/tmp/qemu"));
        std::env::remove_var(ENV_QEMU_PATH);
    }

    #[test]
    fn resolve_optional_paths() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::set_var(ENV_QCOW2_PATH, "/tmp/image.qcow2");
        std::env::set_var(ENV_KERNEL_PATH, "/tmp/kernel");
        std::env::set_var(ENV_INITRD_PATH, "/tmp/initrd");

        assert_eq!(
            resolve_qcow2_path().as_deref(),
            Some(Path::new("/tmp/image.qcow2"))
        );
        let (kernel, initrd) = resolve_kernel_initrd();
        assert_eq!(kernel.as_deref(), Some(Path::new("/tmp/kernel")));
        assert_eq!(initrd.as_deref(), Some(Path::new("/tmp/initrd")));

        std::env::remove_var(ENV_QCOW2_PATH);
        std::env::remove_var(ENV_KERNEL_PATH);
        std::env::remove_var(ENV_INITRD_PATH);
    }

    #[test]
    fn resolve_runtime_dir_defaults() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::remove_var(ENV_RUNTIME_DIR);
        assert_eq!(
            resolve_runtime_dir(),
            PathBuf::from("/run/libvirt/plasmavmc")
        );
    }

    #[test]
    fn resolve_runtime_dir_from_env() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::set_var(ENV_RUNTIME_DIR, "/tmp/plasmavmc-runtime");
        assert_eq!(
            resolve_runtime_dir(),
            PathBuf::from("/tmp/plasmavmc-runtime")
        );
        std::env::remove_var(ENV_RUNTIME_DIR);
    }

    #[test]
    fn resolve_qmp_timeout_defaults() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::remove_var(ENV_QMP_TIMEOUT_SECS);
        assert_eq!(resolve_qmp_timeout_secs(), 15);
    }

    #[test]
    fn resolve_qmp_timeout_from_env() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::set_var(ENV_QMP_TIMEOUT_SECS, "42");
        assert_eq!(resolve_qmp_timeout_secs(), 42);
        std::env::remove_var(ENV_QMP_TIMEOUT_SECS);
    }

    #[test]
    fn resolve_nbd_max_queues_defaults() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::remove_var(ENV_NBD_MAX_QUEUES);
        assert_eq!(resolve_nbd_max_queues(), 16);
    }

    #[test]
    fn resolve_nbd_max_queues_from_env() {
        let _guard = env_test_lock().lock().unwrap();
        std::env::set_var(ENV_NBD_MAX_QUEUES, "12");
        assert_eq!(resolve_nbd_max_queues(), 12);
        std::env::remove_var(ENV_NBD_MAX_QUEUES);
    }
}
