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

use crate::engine::{EngineError, EngineResult};

/// Link the user-facing command to a packaged `bds-cli` binary.
pub fn install_launcher(executable: &Path, home_dir: &Path) -> EngineResult<PathBuf> {
    if !executable.is_file() {
        return Err(EngineError::Validation(format!(
            "installing the CLI requires the packaged bds-cli executable (not found at {})",
            executable.display()
        )));
    }
    let bin_dir = home_dir.join(".local/bin");
    std::fs::create_dir_all(&bin_dir)?;
    let target = bin_dir.join(if cfg!(windows) {
        "bds-cli.exe"
    } else {
        "bds-cli"
    });
    let source = executable.canonicalize()?;
    if let Err(error) = std::fs::remove_file(&target)
        && error.kind() != std::io::ErrorKind::NotFound
    {
        return Err(error.into());
    }

    #[cfg(unix)]
    std::os::unix::fs::symlink(source, &target)?;
    #[cfg(windows)]
    std::os::windows::fs::symlink_file(source, &target)?;
    #[cfg(not(any(unix, windows)))]
    return Err(EngineError::Validation(
        "installing the CLI is not supported on this platform".to_string(),
    ));

    Ok(target)
}

/// Resolve the CLI shipped beside the desktop executable and install it.
pub fn install_packaged_launcher(home_dir: &Path) -> EngineResult<PathBuf> {
    let app = std::env::current_exe()?;
    let cli = app.with_file_name(if cfg!(windows) {
        "bds-cli.exe"
    } else {
        "bds-cli"
    });
    install_launcher(&cli, home_dir)
}

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

    #[cfg(unix)]
    #[test]
    fn install_replaces_existing_launcher_with_symlink() {
        let root = tempfile::tempdir().unwrap();
        let executable = root.path().join("packaged-bds-cli");
        std::fs::write(&executable, b"binary").unwrap();
        let home = root.path().join("home");
        let target = home.join(".local/bin/bds-cli");
        std::fs::create_dir_all(target.parent().unwrap()).unwrap();
        std::fs::write(&target, b"old launcher").unwrap();

        let target = install_launcher(&executable, &home).unwrap();
        assert!(target.is_symlink());
        assert_eq!(
            std::fs::read_link(&target).unwrap(),
            executable.canonicalize().unwrap()
        );
        assert_eq!(install_launcher(&executable, &home).unwrap(), target);

        let replacement = root.path().join("replacement-bds-cli");
        std::fs::write(&replacement, b"new binary").unwrap();
        assert_eq!(install_launcher(&replacement, &home).unwrap(), target);
        assert_eq!(
            std::fs::read_link(target).unwrap(),
            replacement.canonicalize().unwrap()
        );
    }

    #[cfg(unix)]
    #[test]
    fn install_replaces_broken_symlink() {
        let root = tempfile::tempdir().unwrap();
        let executable = root.path().join("packaged-bds-cli");
        std::fs::write(&executable, b"binary").unwrap();
        let home = root.path().join("home");
        let target = home.join(".local/bin/bds-cli");
        std::fs::create_dir_all(target.parent().unwrap()).unwrap();
        std::os::unix::fs::symlink(root.path().join("missing-bds-cli"), &target).unwrap();

        assert_eq!(install_launcher(&executable, &home).unwrap(), target);
        assert!(target.is_symlink());
        assert_eq!(
            std::fs::read_link(target).unwrap(),
            executable.canonicalize().unwrap()
        );
    }
}
