//! Rhai wrappers for OS module functions
//!
//! This module provides Rhai wrappers for the functions in the OS module.
//!
//! For SSH operations, see the [`crate::ssh::rhai`] module.

use super::package::PackHero;
use super::{download as dl, fs, package};
use rhai::{Array, Engine, EvalAltResult, Position};

/// A trait for converting a Result to a Rhai-compatible error
pub trait ToRhaiError<T> {
    fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>>;
}

impl<T, E: std::error::Error> ToRhaiError<T> for Result<T, E> {
    fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
        self.map_err(|e| {
            Box::new(EvalAltResult::ErrorRuntime(
                e.to_string().into(),
                Position::NONE,
            ))
        })
    }
}

/// Register OS module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register file system functions
    engine.register_fn("copy", copy);
    engine.register_fn("copy_bin", copy_bin);
    engine.register_fn("exist", exist);
    engine.register_fn("ensure", ensure);
    engine.register_fn("ensure_dir", ensure_dir);
    engine.register_fn("ensure_file", ensure_file);
    engine.register_fn("find_file", find_file);
    engine.register_fn("find_files", find_files);
    engine.register_fn("find_dir", find_dir);
    engine.register_fn("find_dirs", find_dirs);
    engine.register_fn("delete", delete);
    engine.register_fn("mkdir", mkdir);
    engine.register_fn("file_size", file_size);
    engine.register_fn("rsync", rsync);
    engine.register_fn("chdir", chdir);
    engine.register_fn("file_read", file_read);
    engine.register_fn("file_write", file_write);
    engine.register_fn("file_write_append", file_write_append);
    engine.register_fn("compare", compare);

    // Register command check functions
    engine.register_fn("which", which);
    engine.register_fn("cmd_ensure_exists", cmd_ensure_exists);

    // Register download functions
    engine.register_fn("download", download);
    engine.register_fn("download_file", download_file);
    engine.register_fn("download_install", download_install);
    engine.register_fn("chmod_exec", chmod_exec);

    // Register move function
    engine.register_fn("mv", mv);

    // Register package management functions
    engine.register_fn("package_install", package_install);
    engine.register_fn("package_remove", package_remove);
    engine.register_fn("package_update", package_update);
    engine.register_fn("package_upgrade", package_upgrade);
    engine.register_fn("package_list", package_list);
    engine.register_fn("package_search", package_search);
    engine.register_fn("package_is_installed", package_is_installed);
    engine.register_fn("package_set_debug", package_set_debug);
    engine.register_fn("package_platform", package_platform);

    // Register binary version checker functions
    engine.register_fn("binary_version_checker", binary_version_checker);
    engine.register_fn("check_binary_version", check_binary_version);

    engine.register_fn("parse_version", parse_version);

    // Register binary installation functions
    engine.register_fn("cmd_installer", cmd_installer);
    engine.register_fn("cmd_installer_custom", cmd_installer_custom);

    // Register directory/path functions
    engine.register_fn("dir_ensure", dir_ensure);
    engine.register_fn("path_ensure", path_ensure);

    // Register platform detection functions
    engine.register_fn("platform_is_osx", platform_is_osx);
    engine.register_fn("platform_is_linux", platform_is_linux);
    engine.register_fn("platform_is_arm", platform_is_arm);
    engine.register_fn("platform_is_x86", platform_is_x86);
    engine.register_fn("platform_check_linux_x86", platform_check_linux_x86);
    engine.register_fn("platform_check_macos_arm", platform_check_macos_arm);

    // Register symlink functions
    engine.register_type_with_name::<RhaiSymlinkBuilder>("SymlinkBuilder");
    engine.register_fn("symlink", symlink_new);
    engine.register_fn("source", RhaiSymlinkBuilder::source);
    engine.register_fn("dest", RhaiSymlinkBuilder::dest);
    engine.register_fn("remove_dest", RhaiSymlinkBuilder::remove_dest);
    engine.register_fn("create", RhaiSymlinkBuilder::create);
    engine.register_fn("is_symlink", is_symlink);
    engine.register_fn("symlink_target", symlink_target);

    Ok(())
}

//
// File System Function Wrappers
//

/// Wrapper for fs::copy
///
/// Recursively copy a file or directory from source to destination.
pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
    fs::copy(src, dest).to_rhai_error()
}

/// Wrapper for fs::copy_bin
///
/// Copy a binary to the correct location based on OS and user privileges.
pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> {
    fs::copy_bin(src).to_rhai_error()
}

/// Wrapper for fs::exist
///
/// Check if a file or directory exists.
pub fn exist(path: &str) -> bool {
    fs::exist(path)
}

/// Wrapper for fs::ensure
///
/// Ensure a path exists (file or directory). Returns an error if it doesn't exist.
pub fn ensure(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::ensure(path).to_rhai_error()
}

/// Wrapper for fs::ensure_dir
///
/// Ensure a directory exists. Returns an error if it doesn't exist or is not a directory.
pub fn ensure_dir(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::ensure_dir(path).to_rhai_error()
}

/// Wrapper for fs::ensure_file
///
/// Ensure a file exists. Returns an error if it doesn't exist or is not a file.
pub fn ensure_file(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::ensure_file(path).to_rhai_error()
}

/// Wrapper for fs::find_file
///
/// Find a file in a directory (with support for wildcards).
pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> {
    fs::find_file(dir, filename).to_rhai_error()
}

/// Wrapper for fs::find_files
///
/// Find multiple files in a directory (recursive, with support for wildcards).
pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> {
    let files = fs::find_files(dir, filename).to_rhai_error()?;

    // Convert Vec<String> to Rhai Array
    let mut array = Array::new();
    for file in files {
        array.push(file.into());
    }

    Ok(array)
}

/// Wrapper for fs::find_dir
///
/// Find a directory in a parent directory (with support for wildcards).
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> {
    fs::find_dir(dir, dirname).to_rhai_error()
}

/// Wrapper for fs::find_dirs
///
/// Find multiple directories in a parent directory (recursive, with support for wildcards).
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> {
    let dirs = fs::find_dirs(dir, dirname).to_rhai_error()?;

    // Convert Vec<String> to Rhai Array
    let mut array = Array::new();
    for dir in dirs {
        array.push(dir.into());
    }

    Ok(array)
}

/// Wrapper for fs::delete
///
/// Delete a file or directory (defensive - doesn't error if file doesn't exist).
pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::delete(path).to_rhai_error()
}

/// Wrapper for fs::mkdir
///
/// Create a directory and all parent directories (defensive - doesn't error if directory exists).
pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::mkdir(path).to_rhai_error()
}

/// Wrapper for fs::file_size
///
/// Get the size of a file in bytes.
pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> {
    fs::file_size(path).to_rhai_error()
}

/// Wrapper for fs::rsync
///
/// Sync directories using rsync (or platform equivalent).
pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
    fs::rsync(src, dest).to_rhai_error()
}

/// Wrapper for fs::chdir
///
/// Change the current working directory.
pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::chdir(path).to_rhai_error()
}

/// Wrapper for fs::file_read
///
/// Read the contents of a file.
pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::file_read(path).to_rhai_error()
}

/// Wrapper for fs::file_write
///
/// Write content to a file (creates the file if it doesn't exist, overwrites if it does).
pub fn file_write(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
    fs::file_write(path, content).to_rhai_error()
}

/// Wrapper for fs::file_write_append
///
/// Append content to a file (creates the file if it doesn't exist).
pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
    fs::file_write_append(path, content).to_rhai_error()
}

/// Wrapper for fs::mv
///
/// Move a file or directory from source to destination.
pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
    fs::mv(src, dest).to_rhai_error()
}

/// Wrapper for fs::compare
///
/// Compare the content of a file with an expected string.
pub fn compare(path: &str, expected: &str) -> Result<bool, Box<EvalAltResult>> {
    fs::compare(path, expected).to_rhai_error()
}

//
// Download Function Wrappers
//

/// Wrapper for os::download
///
/// Download a file from URL to destination using the curl command.
pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> {
    dl::download(url, dest, min_size_kb).to_rhai_error()
}

/// Wrapper for os::download_file
///
/// Download a file from URL to a specific file destination using the curl command.
pub fn download_file(
    url: &str,
    dest: &str,
    min_size_kb: i64,
) -> Result<String, Box<EvalAltResult>> {
    dl::download_file(url, dest, min_size_kb).to_rhai_error()
}

/// Wrapper for os::download_install
///
/// Download a file and install it if it's a supported package format.
pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> {
    dl::download_install(url, min_size_kb).to_rhai_error()
}

/// Wrapper for os::chmod_exec
///
/// Make a file executable (equivalent to chmod +x).
pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::chmod_exec(path).to_rhai_error()
}

/// Wrapper for os::package::which
///
/// Check if a command exists in PATH.
pub fn which(cmd: &str) -> rhai::Dynamic {
    match package::which(cmd) {
        Some(path) => path.into(),
        None => rhai::Dynamic::UNIT,
    }
}

/// Wrapper for os::cmd_ensure_exists
///
/// Ensure that one or more commands exist in the system PATH.
/// If any command doesn't exist, an error is thrown.
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
    fs::cmd_ensure_exists(commands).to_rhai_error()
}

//
// Package Management Function Wrappers
//

/// Wrapper for os::package::PackHero::install
///
/// Install a package using the system package manager.
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
    let hero = PackHero::new();
    hero.install(package)
        .map(|_| format!("Package '{}' installed successfully", package))
        .to_rhai_error()
}

/// Wrapper for os::package::PackHero::remove
///
/// Remove a package using the system package manager.
/// Wrapper for os::package::PackHero::package_remove
///
/// Remove packages using the appropriate package manager.
/// Automatically detects platform (Ubuntu/APT or macOS/Homebrew).
/// Supports multiple packages separated by commas.
///
/// # Arguments
///
/// * `packages` - Single package name or comma-separated list
///   - macOS: "go,delve,gopls"
///   - Ubuntu: "golang-go,git"
pub fn package_remove(packages: &str) -> Result<String, Box<EvalAltResult>> {
    let hero = PackHero::new();
    hero.package_remove(packages)
        .map(|_| format!("Package(s) '{}' removed successfully", packages))
        .to_rhai_error()
}

/// Wrapper for os::package::PackHero::update
///
/// Update package lists using the system package manager.
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
    let hero = PackHero::new();
    hero.update()
        .map(|_| "Package lists updated successfully".to_string())
        .to_rhai_error()
}

/// Wrapper for os::package::PackHero::upgrade
///
/// Upgrade installed packages using the system package manager.
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
    let hero = PackHero::new();
    hero.upgrade()
        .map(|_| "Packages upgraded successfully".to_string())
        .to_rhai_error()
}

/// Wrapper for os::package::PackHero::list_installed
///
/// List installed packages using the system package manager.
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
    let hero = PackHero::new();
    let packages = hero.list_installed().to_rhai_error()?;

    // Convert Vec<String> to Rhai Array
    let mut array = Array::new();
    for package in packages {
        array.push(package.into());
    }

    Ok(array)
}

/// Wrapper for os::package::PackHero::search
///
/// Search for packages using the system package manager.
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
    let hero = PackHero::new();
    let packages = hero.search(query).to_rhai_error()?;

    // Convert Vec<String> to Rhai Array
    let mut array = Array::new();
    for package in packages {
        array.push(package.into());
    }

    Ok(array)
}

/// Wrapper for os::package::PackHero::is_installed
///
/// Check if a package is installed using the system package manager.
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
    let hero = PackHero::new();
    hero.is_installed(package).to_rhai_error()
}

/// Wrapper for os::package::BinaryVersionChecker builder
///
/// Create a new binary version checker builder for checking binary installations.
/// Returns a BinaryVersionChecker that can be configured with fluent methods.
///
/// # Example
///
/// ```rhai
/// let checker = binary_version_checker("go version");
/// if checker.min_version("1.21.0").check()? {
///     print("Go version is sufficient");
/// }
/// ```
pub fn binary_version_checker(command: &str) -> package::BinaryVersionChecker {
    package::BinaryVersionChecker::new(command)
}

/// Check if a binary version meets minimum requirements (convenience wrapper)
///
/// This is a simplified wrapper that checks a binary version using the builder pattern internally.
///
/// # Arguments
///
/// * `command` - Full command to execute (e.g., "go version", "rustc --version")
/// * `min_version` - Minimum required version (e.g., "1.21.0")
/// * `output_line_filter` - Optional regex filter to match the output line containing version (empty = no filter)
///
/// # Returns
///
/// * `true` if version check passes, `false` otherwise
///
/// # Example
///
/// ```rhai
/// // Check if Go is at least 1.21.0
/// if check_binary_version("go version", "1.21.0", "go version go")? {
///     print("Go version is sufficient");
/// }
///
/// // Without line filter (regex searches entire output)
/// if check_binary_version("rustc --version", "1.92.0", "")? {
///     print("Rust version is sufficient");
/// }
/// ```
pub fn check_binary_version(
    command: &str,
    min_version: &str,
    output_line_filter: &str,
) -> Result<bool, Box<EvalAltResult>> {
    let mut checker = package::BinaryVersionChecker::new(command).min_version(min_version);

    if !output_line_filter.is_empty() {
        checker = checker.output_line_filter(output_line_filter);
    }

    checker.check().to_rhai_error()
}

/// Wrapper for os::package::parse_version
///
/// Parse semantic version string to comparable u32.
///
/// Examples:
/// - "1.25.5" → 0x011905
/// - "1.24.0" → 0x011800
///
/// Can then compare: parse_version("1.25.5") > parse_version("1.24.0")
pub fn parse_version(version_str: &str) -> i64 {
    package::parse_version(version_str) as i64
}

/// Wrapper for os::package::CmdInstaller - Install a binary with default settings
///
/// Installs a binary to /usr/local/bin with the filename from the source path,
/// and automatically adds the directory to PATH.
///
/// For more control, use cmd_installer_custom.
///
/// # Example
///
/// ```rhai
/// // Simple installation with all defaults
/// cmd_installer("/tmp/go/bin/go");  // Installs to /usr/local/bin/go
/// ```
pub fn cmd_installer(binary_path: &str) -> Result<String, Box<EvalAltResult>> {
    package::CmdInstaller::new(binary_path)
        .install()
        .to_rhai_error()
}

/// Wrapper for os::package::CmdInstaller - Install a binary with custom settings
///
/// # Arguments
///
/// * `binary_path` - Path to the binary to install
/// * `target_name` - Name for the installed binary (empty string = use filename)
/// * `target_dir` - Directory to install to (empty string = /usr/local/bin)
/// * `add_to_path` - Whether to add directory to PATH
///
/// # Example
///
/// ```rhai
/// // Install with custom name and directory
/// cmd_installer_custom("/tmp/mytool", "mytool", "/opt/bin", true);
///
/// // Install without adding to PATH
/// cmd_installer_custom("/tmp/tool", "", "", false);
/// ```
pub fn cmd_installer_custom(
    binary_path: &str,
    target_name: &str,
    target_dir: &str,
    add_to_path: bool,
) -> Result<String, Box<EvalAltResult>> {
    let mut installer = package::CmdInstaller::new(binary_path);

    if !target_name.is_empty() {
        installer = installer.target_name(target_name);
    }

    if !target_dir.is_empty() {
        installer = installer.target_dir(target_dir);
    }

    installer = installer.add_to_path(add_to_path);

    installer.install().to_rhai_error()
}

//
// Directory/Path Ensure Function Wrappers
//

/// Wrapper for os::fs::dir_ensure
///
/// Create a directory and all parent directories if they don't exist.
pub fn dir_ensure(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::dir_ensure(path).to_rhai_error()
}

/// Wrapper for os::fs::path_ensure
///
/// Create a path and all parent directories if they don't exist.
pub fn path_ensure(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::path_ensure(path).to_rhai_error()
}

// Thread-local storage for package debug flag
thread_local! {
    static PACKAGE_DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
}

/// Set the debug mode for package management operations
pub fn package_set_debug(debug: bool) -> bool {
    let mut hero = PackHero::new();
    hero.set_debug(debug);

    // Also set the thread-local debug flag
    PACKAGE_DEBUG.with(|cell| {
        *cell.borrow_mut() = debug;
    });

    debug
}

/// Get the current platform name for package management
pub fn package_platform() -> String {
    let hero = PackHero::new();
    match hero.platform() {
        package::Platform::Ubuntu => "Ubuntu".to_string(),
        package::Platform::MacOS => "MacOS".to_string(),
        package::Platform::Unknown => "Unknown".to_string(),
    }
}

//
// Platform Detection Function Wrappers
//

/// Wrapper for platform::is_osx
pub fn platform_is_osx() -> bool {
    super::platform::is_osx()
}

/// Wrapper for platform::is_linux
pub fn platform_is_linux() -> bool {
    super::platform::is_linux()
}

/// Wrapper for platform::is_arm
pub fn platform_is_arm() -> bool {
    super::platform::is_arm()
}

/// Wrapper for platform::is_x86
pub fn platform_is_x86() -> bool {
    super::platform::is_x86()
}

/// Wrapper for platform::check_linux_x86
pub fn platform_check_linux_x86() -> Result<(), Box<EvalAltResult>> {
    super::platform::check_linux_x86().map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Platform Check Error: {}", e).into(),
            Position::NONE,
        ))
    })
}

/// Wrapper for platform::check_macos_arm
pub fn platform_check_macos_arm() -> Result<(), Box<EvalAltResult>> {
    super::platform::check_macos_arm().map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Platform Check Error: {}", e).into(),
            Position::NONE,
        ))
    })
}

//
// Symlink Function Wrappers
//

/// Rhai-facing symlink builder struct
#[derive(Clone, Debug)]
pub struct RhaiSymlinkBuilder {
    builder: fs::SymlinkBuilder,
}

impl std::fmt::Display for RhaiSymlinkBuilder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "SymlinkBuilder")
    }
}

impl RhaiSymlinkBuilder {
    /// Create a new symlink builder
    pub fn new(source: &str, dest: &str) -> Self {
        Self {
            builder: fs::SymlinkBuilder::new(source, dest),
        }
    }

    /// Set the source path (the target the symlink points to)
    pub fn source(mut self, source: &str) -> Self {
        self.builder = self.builder.source(source);
        self
    }

    /// Set the destination path (where the symlink will be created)
    pub fn dest(mut self, dest: &str) -> Self {
        self.builder = self.builder.dest(dest);
        self
    }

    /// If true, remove the destination if it already exists
    pub fn remove_dest(mut self, remove: bool) -> Self {
        self.builder = self.builder.remove_dest(remove);
        self
    }

    /// Create the symbolic link
    pub fn create(self) -> Result<String, Box<EvalAltResult>> {
        self.builder.create().to_rhai_error()
    }
}

/// Create a new symlink builder
///
/// # Arguments
///
/// * `source` - The path that the symlink will point to
/// * `dest` - The path where the symlink will be created
///
/// # Example
///
/// ```rhai
/// symlink("/usr/bin/python3", "/usr/local/bin/python")
///     .remove_dest(true)
///     .create()?;
/// ```
pub fn symlink_new(source: &str, dest: &str) -> RhaiSymlinkBuilder {
    RhaiSymlinkBuilder::new(source, dest)
}

/// Check if a path is a symbolic link
///
/// # Arguments
///
/// * `path` - The path to check
///
/// # Returns
///
/// * `bool` - True if the path is a symlink, false otherwise
pub fn is_symlink(path: &str) -> bool {
    fs::is_symlink(path)
}

/// Get the target of a symbolic link
///
/// # Arguments
///
/// * `path` - The path to the symlink
///
/// # Returns
///
/// * `String` - The target path the symlink points to
pub fn symlink_target(path: &str) -> Result<String, Box<EvalAltResult>> {
    fs::symlink_target(path).to_rhai_error()
}
