// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore memfd_create prefixcat rsplit testcat

use std::ffi::{OsStr, OsString};
use std::io::{Write, stderr};
use std::path::{Path, PathBuf};
use std::process;

use uucore::Args;
use uucore::display::Quotable;
use uucore::locale;

/// Gets all available utilities including "coreutils"
#[allow(clippy::type_complexity)]
pub fn get_all_utilities<T: Args>(
    util_map: &phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> clap::Command)>,
) -> Vec<&'static str> {
    std::iter::once("coreutils")
        .chain(util_map.keys().copied())
        .collect()
}

/// Prints a "utility not found" error and exits
pub fn not_found(util: &OsStr) -> ! {
    let _ = writeln!(
        stderr(),
        "coreutils: unknown program '{}'",
        util.maybe_quote()
    );
    process::exit(1);
}

/// Prints an "unrecognized option" error and exits
pub fn unrecognized_option(binary_name: &str, option: &OsStr) -> ! {
    let _ = writeln!(
        stderr(),
        "{binary_name}: unrecognized option '{}'",
        option.to_string_lossy()
    );
    process::exit(1);
}

/// Sets up localization for a utility with proper error handling
pub fn setup_localization_or_exit(util_name: &str) {
    let util_name = get_canonical_util_name(util_name);
    locale::setup_localization(util_name).unwrap_or_else(|err| {
        match err {
            locale::LocalizationError::ParseResource {
                error: err_msg,
                snippet,
            } => eprintln!("Localization parse error at {snippet}: {err_msg}"),
            other => eprintln!("Could not init the localization system: {other}"),
        }
        process::exit(99)
    });
}

/// Gets the canonical utility name, resolving aliases
fn get_canonical_util_name(util_name: &str) -> &str {
    match util_name {
        // uu_test aliases - '[' is an alias for test
        "[" => "test",
        "dir" => "ls",  // dir is an alias for ls
        "vdir" => "ls", // vdir is an alias for ls

        // Default case - return the util name as is
        _ => util_name,
    }
}

/// Gets the binary path from command line arguments
/// Panics if the binary path cannot be determined
#[cfg(any(
    not(any(target_os = "linux", target_os = "android")),
    target_env = "musl"
))]
pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
    match args.next() {
        Some(ref s) if !s.is_empty() => PathBuf::from(s),
        // the fallback is valid only for hardlinks
        _ => std::env::current_exe().unwrap(),
    }
}
/// Get actual binary path from kernel, not argv0, to prevent `env -a` from bypassing
/// AppArmor, SELinux policies on hard-linked binaries
#[cfg(all(
    any(target_os = "linux", target_os = "android"),
    not(target_env = "musl")
))]
pub fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
    use std::fs::File;
    use std::io::Read;
    use std::os::unix::ffi::OsStrExt;
    let execfn = rustix::param::linux_execfn();
    let execfn_bytes = execfn.to_bytes();
    let exec_path = Path::new(OsStr::from_bytes(execfn_bytes));
    let argv0 = args.next().unwrap();
    let mut shebang_buf = [0u8; 2];
    // exec_path is wrong when called from shebang or memfd_create (/proc/self/fd/*)
    // argv0 is not full-path when called from PATH
    if execfn_bytes.rsplit(|&b| b == b'/').next() == argv0.as_bytes().rsplit(|&b| b == b'/').next()
        || execfn_bytes.starts_with(b"/proc/")
        || (File::open(Path::new(exec_path))
            .and_then(|mut f| f.read_exact(&mut shebang_buf))
            .is_ok()
            && &shebang_buf == b"#!")
    {
        argv0.into()
    } else {
        exec_path.into()
    }
}
/// Extracts the binary name from a path
pub fn name(binary_path: &Path) -> Option<&str> {
    binary_path.file_stem()?.to_str()
}

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

    #[test]
    fn test_get_canonical_util_name() {
        // Test a few key aliases
        assert_eq!(get_canonical_util_name("["), "test");
        assert_eq!(get_canonical_util_name("dir"), "ls");

        // Test passthrough case
        assert_eq!(get_canonical_util_name("cat"), "cat");
    }

    #[test]
    fn test_name() {
        // Test normal executable name
        assert_eq!(name(Path::new("/usr/bin/ls")), Some("ls"));
        assert_eq!(name(Path::new("cat")), Some("cat"));
        assert_eq!(
            name(Path::new("./target/debug/coreutils")),
            Some("coreutils")
        );

        // Test with extensions
        assert_eq!(name(Path::new("program.exe")), Some("program"));
        assert_eq!(name(Path::new("/path/to/utility.bin")), Some("utility"));

        // Test edge cases
        assert_eq!(name(Path::new("")), None);
        assert_eq!(name(Path::new("/")), None);
    }
}
