use dirs;
use libc;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;

// Define a custom error type for file system operations
#[derive(Debug)]
pub enum FsError {
    DirectoryNotFound(String),
    FileNotFound(String),
    CreateDirectoryFailed(io::Error),
    CopyFailed(io::Error),
    DeleteFailed(io::Error),
    CommandFailed(String),
    CommandNotFound(String),
    CommandExecutionError(io::Error),
    InvalidGlobPattern(glob::PatternError),
    NotADirectory(String),
    NotAFile(String),
    UnknownFileType(String),
    MetadataError(io::Error),
    ChangeDirFailed(io::Error),
    ReadFailed(io::Error),
    WriteFailed(io::Error),
    AppendFailed(io::Error),
}

// Implement Display for FsError
impl fmt::Display for FsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FsError::DirectoryNotFound(dir) => write!(f, "Directory '{}' does not exist", dir),
            FsError::FileNotFound(pattern) => write!(f, "No files found matching '{}'", pattern),
            FsError::CreateDirectoryFailed(e) => {
                write!(f, "Failed to create parent directories: {}", e)
            }
            FsError::CopyFailed(e) => write!(f, "Failed to copy file: {}", e),
            FsError::DeleteFailed(e) => write!(f, "Failed to delete: {}", e),
            FsError::CommandFailed(e) => write!(f, "{}", e),
            FsError::CommandNotFound(e) => write!(f, "Command not found: {}", e),
            FsError::CommandExecutionError(e) => write!(f, "Failed to execute command: {}", e),
            FsError::InvalidGlobPattern(e) => write!(f, "Invalid glob pattern: {}", e),
            FsError::NotADirectory(path) => {
                write!(f, "Path '{}' exists but is not a directory", path)
            }
            FsError::NotAFile(path) => write!(f, "Path '{}' is not a regular file", path),
            FsError::UnknownFileType(path) => write!(f, "Unknown file type at '{}'", path),
            FsError::MetadataError(e) => write!(f, "Failed to get file metadata: {}", e),
            FsError::ChangeDirFailed(e) => write!(f, "Failed to change directory: {}", e),
            FsError::ReadFailed(e) => write!(f, "Failed to read file: {}", e),
            FsError::WriteFailed(e) => write!(f, "Failed to write to file: {}", e),
            FsError::AppendFailed(e) => write!(f, "Failed to append to file: {}", e),
        }
    }
}

// Implement Error trait for FsError
impl Error for FsError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            FsError::CreateDirectoryFailed(e) => Some(e),
            FsError::CopyFailed(e) => Some(e),
            FsError::DeleteFailed(e) => Some(e),
            FsError::CommandExecutionError(e) => Some(e),
            FsError::InvalidGlobPattern(e) => Some(e),
            FsError::MetadataError(e) => Some(e),
            FsError::ChangeDirFailed(e) => Some(e),
            FsError::ReadFailed(e) => Some(e),
            FsError::WriteFailed(e) => Some(e),
            FsError::AppendFailed(e) => Some(e),
            _ => None,
        }
    }
}

#[cfg(not(target_os = "windows"))]
fn set_executable(path: &Path) -> Result<(), io::Error> {
    let mut perms = fs::metadata(path)?.permissions();
    perms.set_mode(0o755); // rwxr-xr-x
    fs::set_permissions(path, perms)
}

fn copy_internal(src: &str, dest: &str, make_executable: bool) -> Result<String, FsError> {
    let dest_path = Path::new(dest);

    // Check if source path contains wildcards
    if src.contains('*') || src.contains('?') || src.contains('[') {
        // Create parent directories for destination if needed
        if let Some(parent) = dest_path.parent() {
            fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
        }

        // Use glob to expand wildcards
        let entries = glob::glob(src).map_err(FsError::InvalidGlobPattern)?;

        let paths: Vec<_> = entries.filter_map(Result::ok).collect();

        if paths.is_empty() {
            return Err(FsError::FileNotFound(src.to_string()));
        }

        let mut success_count = 0;
        let dest_is_dir = dest_path.exists() && dest_path.is_dir();

        for path in paths {
            let target_path = if dest_is_dir {
                // If destination is a directory, copy the file into it
                if path.is_file() {
                    // For files, just use the filename
                    dest_path.join(path.file_name().unwrap_or_default())
                } else if path.is_dir() {
                    // For directories, use the directory name
                    dest_path.join(path.file_name().unwrap_or_default())
                } else {
                    // Fallback
                    dest_path.join(path.file_name().unwrap_or_default())
                }
            } else {
                // Otherwise use the destination as is (only makes sense for single file)
                dest_path.to_path_buf()
            };

            if path.is_file() {
                // Copy file
                if let Err(e) = fs::copy(&path, &target_path) {
                    println!("Warning: Failed to copy {}: {}", path.display(), e);
                } else {
                    success_count += 1;
                    if make_executable {
                        #[cfg(not(target_os = "windows"))]
                        {
                            if let Err(e) = set_executable(&target_path) {
                                println!(
                                    "Warning: Failed to make {} executable: {}",
                                    target_path.display(),
                                    e
                                );
                            }
                        }
                    }
                }
            } else if path.is_dir() {
                // For directories, use platform-specific command
                #[cfg(target_os = "windows")]
                let output = Command::new("xcopy")
                    .args(&[
                        "/E",
                        "/I",
                        "/H",
                        "/Y",
                        &path.to_string_lossy(),
                        &target_path.to_string_lossy(),
                    ])
                    .status();

                #[cfg(not(target_os = "windows"))]
                let output = Command::new("cp")
                    .args(&[
                        "-R",
                        &path.to_string_lossy(),
                        &target_path.to_string_lossy(),
                    ])
                    .status();

                match output {
                    Ok(status) => {
                        if status.success() {
                            success_count += 1;
                        }
                    }
                    Err(e) => println!(
                        "Warning: Failed to copy directory {}: {}",
                        path.display(),
                        e
                    ),
                }
            }
        }

        if success_count > 0 {
            Ok(format!(
                "Successfully copied {} items from '{}' to '{}'",
                success_count, src, dest
            ))
        } else {
            Err(FsError::CommandFailed(format!(
                "Failed to copy any files from '{}' to '{}'",
                src, dest
            )))
        }
    } else {
        // Handle non-wildcard paths normally
        let src_path = Path::new(src);

        // Check if source exists
        if !src_path.exists() {
            return Err(FsError::FileNotFound(src.to_string()));
        }

        // Create parent directories if they don't exist
        if let Some(parent) = dest_path.parent() {
            fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
        }

        // Copy based on source type
        if src_path.is_file() {
            // If destination is a directory, copy the file into it
            if dest_path.exists() && dest_path.is_dir() {
                let file_name = src_path.file_name().unwrap_or_default();
                let new_dest_path = dest_path.join(file_name);
                fs::copy(src_path, &new_dest_path).map_err(FsError::CopyFailed)?;
                if make_executable {
                    #[cfg(not(target_os = "windows"))]
                    {
                        if let Err(e) = set_executable(&new_dest_path) {
                            println!(
                                "Warning: Failed to make {} executable: {}",
                                new_dest_path.display(),
                                e
                            );
                        }
                    }
                }
                Ok(format!(
                    "Successfully copied file '{}' to '{}/{}'",
                    src,
                    dest,
                    file_name.to_string_lossy()
                ))
            } else {
                // Otherwise copy file to the specified destination
                fs::copy(src_path, dest_path).map_err(FsError::CopyFailed)?;
                if make_executable {
                    #[cfg(not(target_os = "windows"))]
                    {
                        if let Err(e) = set_executable(dest_path) {
                            println!(
                                "Warning: Failed to make {} executable: {}",
                                dest_path.display(),
                                e
                            );
                        }
                    }
                }
                Ok(format!("Successfully copied file '{}' to '{}'", src, dest))
            }
        } else if src_path.is_dir() {
            // For directories, use platform-specific command
            #[cfg(target_os = "windows")]
            let output = Command::new("xcopy")
                .args(&["/E", "/I", "/H", "/Y", src, dest])
                .output();

            #[cfg(not(target_os = "windows"))]
            let output = Command::new("cp").args(&["-R", src, dest]).output();

            match output {
                Ok(out) => {
                    if out.status.success() {
                        Ok(format!(
                            "Successfully copied directory '{}' to '{}'",
                            src, dest
                        ))
                    } else {
                        let error = String::from_utf8_lossy(&out.stderr);
                        Err(FsError::CommandFailed(format!(
                            "Failed to copy directory: {}",
                            error
                        )))
                    }
                }
                Err(e) => Err(FsError::CommandExecutionError(e)),
            }
        } else {
            Err(FsError::UnknownFileType(src.to_string()))
        }
    }
}

/**
 * Recursively copy a file or directory from source to destination.
 *
 * # Arguments
 *
 * * `src` - The source path, which can include wildcards
 * * `dest` - The destination path
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating what was copied
 * * `Err(FsError)` - An error if the copy operation failed
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::copy;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     // Copy a single file
 *     let result = copy("file.txt", "backup/file.txt")?;
 *
 *     // Copy multiple files using wildcards
 *     let result = copy("*.txt", "backup/")?;
 *
 *     // Copy a directory recursively
 *     let result = copy("src_dir", "dest_dir")?;
 *
 *     Ok(())
 * }
 * ```
 */
pub fn copy(src: &str, dest: &str) -> Result<String, FsError> {
    copy_internal(src, dest, false)
}

/**
 * Copy a binary to the correct location based on OS and user privileges.
 *
 * # Arguments
 *
 * * `src` - The source file path
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating where the file was copied
 * * `Err(FsError)` - An error if the copy operation failed
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::copy_bin;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     // Copy a binary
 *     let result = copy_bin("my_binary")?;
 *     Ok(())
 * }
 * ```
 */
pub fn copy_bin(src: &str) -> Result<String, FsError> {
    let dest_path = if cfg!(target_os = "linux") && unsafe { libc::getuid() } == 0 {
        Path::new("/usr/local/bin").to_path_buf()
    } else {
        dirs::home_dir()
            .ok_or_else(|| FsError::DirectoryNotFound("Home directory not found".to_string()))?
            .join("hero/bin")
    };

    let result = copy_internal(src, dest_path.to_str().unwrap(), true);
    if let Ok(msg) = &result {
        println!("{}", msg);
    }
    result
}

/**
 * Check if a file or directory exists.
 *
 * # Arguments
 *
 * * `path` - The path to check
 *
 * # Returns
 *
 * * `bool` - True if the path exists, false otherwise
 *
 * # Examples
 *
 * ```
 * use herosal_os::exist;
 *
 * if exist("file.txt") {
 *     println!("File exists");
 * }
 * ```
 */
pub fn exist(path: &str) -> bool {
    Path::new(path).exists()
}

/**
 * Ensure a path exists (file or directory). Returns an error if it doesn't exist.
 *
 * # Arguments
 *
 * * `path` - The path to check
 *
 * # Returns
 *
 * * `Ok(String)` - A success message if the path exists
 * * `Err(FsError)` - An error if the path doesn't exist
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::ensure;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     ensure("/path/to/something")?;
 *     println!("Path exists!");
 *     Ok(())
 * }
 * ```
 */
pub fn ensure(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);
    if !path_obj.exists() {
        return Err(FsError::FileNotFound(path.to_string()));
    }
    Ok(format!("Path '{}' exists", path))
}

/**
 * Ensure a directory exists. Returns an error if it doesn't exist or is not a directory.
 *
 * # Arguments
 *
 * * `path` - The path to check
 *
 * # Returns
 *
 * * `Ok(String)` - A success message if the directory exists
 * * `Err(FsError)` - An error if the path doesn't exist or is not a directory
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::ensure_dir;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     ensure_dir("/path/to/directory")?;
 *     println!("Directory exists!");
 *     Ok(())
 * }
 * ```
 */
pub fn ensure_dir(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);
    if !path_obj.exists() {
        return Err(FsError::DirectoryNotFound(path.to_string()));
    }
    if !path_obj.is_dir() {
        return Err(FsError::NotADirectory(path.to_string()));
    }
    Ok(format!("Directory '{}' exists", path))
}

/**
 * Ensure a file exists. Returns an error if it doesn't exist or is not a file.
 *
 * # Arguments
 *
 * * `path` - The path to check
 *
 * # Returns
 *
 * * `Ok(String)` - A success message if the file exists
 * * `Err(FsError)` - An error if the path doesn't exist or is not a file
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::ensure_file;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     ensure_file("/path/to/file.txt")?;
 *     println!("File exists!");
 *     Ok(())
 * }
 * ```
 */
pub fn ensure_file(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);
    if !path_obj.exists() {
        return Err(FsError::FileNotFound(path.to_string()));
    }
    if !path_obj.is_file() {
        return Err(FsError::NotAFile(path.to_string()));
    }
    Ok(format!("File '{}' exists", path))
}

/**
 * Find a file in a directory (with support for wildcards).
 *
 * # Arguments
 *
 * * `dir` - The directory to search in
 * * `filename` - The filename pattern to search for (can include wildcards)
 *
 * # Returns
 *
 * * `Ok(String)` - The path to the found file
 * * `Err(FsError)` - An error if no file is found or multiple files are found
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::find_file;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let file_path = find_file("/path/to/dir", "*.txt")?;
 *     println!("Found file: {}", file_path);
 *     Ok(())
 * }
 * ```
 */
pub fn find_file(dir: &str, filename: &str) -> Result<String, FsError> {
    let dir_path = Path::new(dir);

    // Check if directory exists
    if !dir_path.exists() || !dir_path.is_dir() {
        return Err(FsError::DirectoryNotFound(dir.to_string()));
    }

    // Use glob to find files - use recursive pattern to find in subdirectories too
    let pattern = format!("{}/**/{}", dir, filename);
    let entries = glob::glob(&pattern).map_err(FsError::InvalidGlobPattern)?;

    let files: Vec<_> = entries
        .filter_map(Result::ok)
        .filter(|path| path.is_file())
        .collect();

    match files.len() {
        0 => Err(FsError::FileNotFound(filename.to_string())),
        1 => Ok(files[0].to_string_lossy().to_string()),
        _ => {
            // If multiple matches, just return the first one instead of erroring
            // This makes wildcard searches more practical
            println!(
                "Note: Multiple files found matching '{}', returning first match",
                filename
            );
            Ok(files[0].to_string_lossy().to_string())
        }
    }
}

/**
 * Find multiple files in a directory (recursive, with support for wildcards).
 *
 * # Arguments
 *
 * * `dir` - The directory to search in
 * * `filename` - The filename pattern to search for (can include wildcards)
 *
 * # Returns
 *
 * * `Ok(Vec<String>)` - A vector of paths to the found files
 * * `Err(FsError)` - An error if the directory doesn't exist or the pattern is invalid
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::find_files;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let files = find_files("/path/to/dir", "*.txt")?;
 *     for file in files {
 *         println!("Found file: {}", file);
 *     }
 *     Ok(())
 * }
 * ```
 */
pub fn find_files(dir: &str, filename: &str) -> Result<Vec<String>, FsError> {
    let dir_path = Path::new(dir);

    // Check if directory exists
    if !dir_path.exists() || !dir_path.is_dir() {
        return Err(FsError::DirectoryNotFound(dir.to_string()));
    }

    // Use glob to find files
    let pattern = format!("{}/**/{}", dir, filename);
    let entries = glob::glob(&pattern).map_err(FsError::InvalidGlobPattern)?;

    let files: Vec<String> = entries
        .filter_map(Result::ok)
        .filter(|path| path.is_file())
        .map(|path| path.to_string_lossy().to_string())
        .collect();

    Ok(files)
}

/**
 * Find a directory in a parent directory (with support for wildcards).
 *
 * # Arguments
 *
 * * `dir` - The parent directory to search in
 * * `dirname` - The directory name pattern to search for (can include wildcards)
 *
 * # Returns
 *
 * * `Ok(String)` - The path to the found directory
 * * `Err(FsError)` - An error if no directory is found or multiple directories are found
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::find_dir;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let dir_path = find_dir("/path/to/parent", "sub*")?;
 *     println!("Found directory: {}", dir_path);
 *     Ok(())
 * }
 * ```
 */
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, FsError> {
    let dir_path = Path::new(dir);

    // Check if directory exists
    if !dir_path.exists() || !dir_path.is_dir() {
        return Err(FsError::DirectoryNotFound(dir.to_string()));
    }

    // Use glob to find directories
    let pattern = format!("{}/{}", dir, dirname);
    let entries = glob::glob(&pattern).map_err(FsError::InvalidGlobPattern)?;

    let dirs: Vec<_> = entries
        .filter_map(Result::ok)
        .filter(|path| path.is_dir())
        .collect();

    match dirs.len() {
        0 => Err(FsError::DirectoryNotFound(dirname.to_string())),
        1 => Ok(dirs[0].to_string_lossy().to_string()),
        _ => Err(FsError::CommandFailed(format!(
            "Multiple directories found matching '{}', expected only one",
            dirname
        ))),
    }
}

/**
 * Find multiple directories in a parent directory (recursive, with support for wildcards).
 *
 * # Arguments
 *
 * * `dir` - The parent directory to search in
 * * `dirname` - The directory name pattern to search for (can include wildcards)
 *
 * # Returns
 *
 * * `Ok(Vec<String>)` - A vector of paths to the found directories
 * * `Err(FsError)` - An error if the parent directory doesn't exist or the pattern is invalid
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::find_dirs;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let dirs = find_dirs("/path/to/parent", "sub*")?;
 *     for dir in dirs {
 *         println!("Found directory: {}", dir);
 *     }
 *     Ok(())
 * }
 * ```
 */
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Vec<String>, FsError> {
    let dir_path = Path::new(dir);

    // Check if directory exists
    if !dir_path.exists() || !dir_path.is_dir() {
        return Err(FsError::DirectoryNotFound(dir.to_string()));
    }

    // Use glob to find directories
    let pattern = format!("{}/**/{}", dir, dirname);
    let entries = glob::glob(&pattern).map_err(FsError::InvalidGlobPattern)?;

    let dirs: Vec<String> = entries
        .filter_map(Result::ok)
        .filter(|path| path.is_dir())
        .map(|path| path.to_string_lossy().to_string())
        .collect();

    Ok(dirs)
}

/**
 * Delete a file or directory (defensive - doesn't error if file doesn't exist).
 *
 * # Arguments
 *
 * * `path` - The path to delete
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating what was deleted
 * * `Err(FsError)` - An error if the deletion failed
 *
 * # Examples
 *
 * ```
 * use herosal_os::delete;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     // Delete a file
 *     let result = delete("file.txt")?;
 *
 *     // Delete a directory and all its contents
 *     let result = delete("directory/")?;
 *
 *     Ok(())
 * }
 * ```
 */
pub fn delete(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Check if path exists
    if !path_obj.exists() {
        return Ok(format!("Nothing to delete at '{}'", path));
    }

    // Delete based on path type
    if path_obj.is_file() || path_obj.is_symlink() {
        fs::remove_file(path_obj).map_err(FsError::DeleteFailed)?;
        Ok(format!("Successfully deleted file '{}'", path))
    } else if path_obj.is_dir() {
        fs::remove_dir_all(path_obj).map_err(FsError::DeleteFailed)?;
        Ok(format!("Successfully deleted directory '{}'", path))
    } else {
        Err(FsError::UnknownFileType(path.to_string()))
    }
}

/**
 * Create a directory and all parent directories (defensive - doesn't error if directory exists).
 *
 * # Arguments
 *
 * * `path` - The path of the directory to create
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating the directory was created
 * * `Err(FsError)` - An error if the creation failed
 *
 * # Examples
 *
 * ```
 * use herosal_os::mkdir;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = mkdir("path/to/new/directory")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn mkdir(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Check if path already exists
    if path_obj.exists() {
        if path_obj.is_dir() {
            return Ok(format!("Directory '{}' already exists", path));
        } else {
            return Err(FsError::NotADirectory(path.to_string()));
        }
    }

    // Create directory and parents
    fs::create_dir_all(path_obj).map_err(FsError::CreateDirectoryFailed)?;
    Ok(format!("Successfully created directory '{}'", path))
}

/**
 * Get the size of a file in bytes.
 *
 * # Arguments
 *
 * * `path` - The path of the file
 *
 * # Returns
 *
 * * `Ok(i64)` - The size of the file in bytes
 * * `Err(FsError)` - An error if the file doesn't exist or isn't a regular file
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::file_size;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let size = file_size("file.txt")?;
 *     println!("File size: {} bytes", size);
 *     Ok(())
 * }
 * ```
 */
pub fn file_size(path: &str) -> Result<i64, FsError> {
    let path_obj = Path::new(path);

    // Check if file exists
    if !path_obj.exists() {
        return Err(FsError::FileNotFound(path.to_string()));
    }

    // Check if it's a regular file
    if !path_obj.is_file() {
        return Err(FsError::NotAFile(path.to_string()));
    }

    // Get file metadata
    let metadata = fs::metadata(path_obj).map_err(FsError::MetadataError)?;
    Ok(metadata.len() as i64)
}

/**
 * Sync directories using rsync (or platform equivalent).
 *
 * # Arguments
 *
 * * `src` - The source directory
 * * `dest` - The destination directory
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating the directories were synced
 * * `Err(FsError)` - An error if the sync failed
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::rsync;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = rsync("source_dir/", "backup_dir/")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn rsync(src: &str, dest: &str) -> Result<String, FsError> {
    let src_path = Path::new(src);
    let dest_path = Path::new(dest);

    // Check if source exists
    if !src_path.exists() {
        return Err(FsError::FileNotFound(src.to_string()));
    }

    // Create parent directories if they don't exist
    if let Some(parent) = dest_path.parent() {
        fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
    }

    // Use platform-specific command for syncing
    #[cfg(target_os = "windows")]
    let output = Command::new("robocopy")
        .args(&[src, dest, "/MIR", "/NFL", "/NDL"])
        .output();

    #[cfg(any(target_os = "macos", target_os = "linux"))]
    let output = Command::new("rsync")
        .args(&["-a", "--delete", src, dest])
        .output();

    match output {
        Ok(out) => {
            if out.status.success() || out.status.code() == Some(1) {
                // rsync and robocopy return 1 for some non-error cases
                Ok(format!("Successfully synced '{}' to '{}'", src, dest))
            } else {
                let error = String::from_utf8_lossy(&out.stderr);
                Err(FsError::CommandFailed(format!(
                    "Failed to sync directories: {}",
                    error
                )))
            }
        }
        Err(e) => Err(FsError::CommandExecutionError(e)),
    }
}

/**
 * Change the current working directory.
 *
 * # Arguments
 *
 * * `path` - The path to change to
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating the directory was changed
 * * `Err(FsError)` - An error if the directory change failed
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::chdir;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = chdir("/path/to/directory")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn chdir(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Check if directory exists
    if !path_obj.exists() {
        return Err(FsError::DirectoryNotFound(path.to_string()));
    }

    // Check if it's a directory
    if !path_obj.is_dir() {
        return Err(FsError::NotADirectory(path.to_string()));
    }

    // Change directory
    std::env::set_current_dir(path_obj).map_err(FsError::ChangeDirFailed)?;

    Ok(format!("Successfully changed directory to '{}'", path))
}

/**
 * Read the contents of a file.
 *
 * # Arguments
 *
 * * `path` - The path of the file to read
 *
 * # Returns
 *
 * * `Ok(String)` - The contents of the file
 * * `Err(FsError)` - An error if the file doesn't exist or can't be read
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::file_read;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let content = file_read("file.txt")?;
 *     println!("File content: {}", content);
 *     Ok(())
 * }
 * ```
 */
pub fn file_read(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Check if file exists
    if !path_obj.exists() {
        return Err(FsError::FileNotFound(path.to_string()));
    }

    // Check if it's a regular file
    if !path_obj.is_file() {
        return Err(FsError::NotAFile(path.to_string()));
    }

    // Read file content
    fs::read_to_string(path_obj).map_err(FsError::ReadFailed)
}

/**
 * Write content to a file (creates the file if it doesn't exist, overwrites if it does).
 *
 * # Arguments
 *
 * * `path` - The path of the file to write to
 * * `content` - The content to write to the file
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating the file was written
 * * `Err(FsError)` - An error if the file can't be written
 *
 * # Examples
 *
 * ```
 * use herosal_os::file_write;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = file_write("file.txt", "Hello, world!")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn file_write(path: &str, content: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Create parent directories if they don't exist
    if let Some(parent) = path_obj.parent() {
        fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
    }

    // Write content to file
    fs::write(path_obj, content).map_err(FsError::WriteFailed)?;

    Ok(format!("Successfully wrote to file '{}'", path))
}

/**
 * Append content to a file (creates the file if it doesn't exist).
 *
 * # Arguments
 *
 * * `path` - The path of the file to append to
 * * `content` - The content to append to the file
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating the content was appended
 * * `Err(FsError)` - An error if the file can't be appended to
 *
 * # Examples
 *
 * ```
 * use herosal_os::file_write_append;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = file_write_append("log.txt", "New log entry\n")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn file_write_append(path: &str, content: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Create parent directories if they don't exist
    if let Some(parent) = path_obj.parent() {
        fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
    }

    // Open file in append mode (or create if it doesn't exist)
    let mut file = fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(path_obj)
        .map_err(FsError::AppendFailed)?;

    // Append content to file
    use std::io::Write;
    file.write_all(content.as_bytes())
        .map_err(FsError::AppendFailed)?;

    Ok(format!("Successfully appended to file '{}'", path))
}

/**
 * Move a file or directory from source to destination.
 *
 * # Arguments
 *
 * * `src` - The source path
 * * `dest` - The destination path
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating what was moved
 * * `Err(FsError)` - An error if the move operation failed
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::mv;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     // Move a file
 *     let result = mv("file.txt", "new_location/file.txt")?;
 *
 *     // Move a directory
 *     let result = mv("src_dir", "dest_dir")?;
 *
 *     // Rename a file
 *     let result = mv("old_name.txt", "new_name.txt")?;
 *
 *     Ok(())
 * }
 * ```
 */
pub fn mv(src: &str, dest: &str) -> Result<String, FsError> {
    let src_path = Path::new(src);
    let dest_path = Path::new(dest);

    // Check if source exists
    if !src_path.exists() {
        return Err(FsError::FileNotFound(src.to_string()));
    }

    // Create parent directories if they don't exist
    if let Some(parent) = dest_path.parent() {
        fs::create_dir_all(parent).map_err(FsError::CreateDirectoryFailed)?;
    }

    // Handle the case where destination is a directory and exists
    let final_dest_path = if dest_path.exists() && dest_path.is_dir() && src_path.is_file() {
        // If destination is a directory and source is a file, move the file into the directory
        let file_name = src_path.file_name().unwrap_or_default();
        dest_path.join(file_name)
    } else {
        dest_path.to_path_buf()
    };

    // Clone the path for use in the error handler
    let final_dest_path_clone = final_dest_path.clone();

    // Perform the move operation
    fs::rename(src_path, &final_dest_path).map_err(|e| {
        // If rename fails (possibly due to cross-device link), try copy and delete
        if e.kind() == std::io::ErrorKind::CrossesDevices {
            // For cross-device moves, we need to copy and then delete
            if src_path.is_file() {
                // Copy file
                match fs::copy(src_path, &final_dest_path_clone) {
                    Ok(_) => {
                        // Delete source after successful copy
                        if let Err(del_err) = fs::remove_file(src_path) {
                            return FsError::DeleteFailed(del_err);
                        }
                        return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
                    }
                    Err(copy_err) => return FsError::CopyFailed(copy_err),
                }
            } else if src_path.is_dir() {
                // For directories, use platform-specific command
                #[cfg(target_os = "windows")]
                let output = Command::new("xcopy")
                    .args(&["/E", "/I", "/H", "/Y", src, dest])
                    .status();

                #[cfg(not(target_os = "windows"))]
                let output = Command::new("cp").args(&["-R", src, dest]).status();

                match output {
                    Ok(status) => {
                        if status.success() {
                            // Delete source after successful copy
                            if let Err(del_err) = fs::remove_dir_all(src_path) {
                                return FsError::DeleteFailed(del_err);
                            }
                            return FsError::CommandFailed("".to_string()); // This is a hack to trigger the success message
                        } else {
                            return FsError::CommandFailed(
                                "Failed to copy directory for move operation".to_string(),
                            );
                        }
                    }
                    Err(cmd_err) => return FsError::CommandExecutionError(cmd_err),
                }
            }
        }
        FsError::CommandFailed(format!("Failed to move '{}' to '{}': {}", src, dest, e))
    })?;

    // If we get here, either the rename was successful or our copy-delete hack worked
    if src_path.is_file() {
        Ok(format!("Successfully moved file '{}' to '{}'", src, dest))
    } else {
        Ok(format!(
            "Successfully moved directory '{}' to '{}'",
            src, dest
        ))
    }
}

/**
 * Check if a command exists in the system PATH.
 *
 * # Arguments
 *
 * * `command` - The command to check
 *
 * # Returns
 *
 * * `String` - Empty string if the command doesn't exist, path to the command if it does
 *
 * # Examples
 *
 * ```
 * use herosal_os::which;
 *
 * let cmd_path = which("ls");
 * if cmd_path != "" {
 *     println!("ls is available at: {}", cmd_path);
 * }
 * ```
 */
pub fn which(command: &str) -> String {
    // Use the appropriate command based on the platform
    #[cfg(target_os = "windows")]
    let output = Command::new("where").arg(command).output();

    #[cfg(not(target_os = "windows"))]
    let output = Command::new("which").arg(command).output();

    match output {
        Ok(out) => {
            if out.status.success() {
                let path = String::from_utf8_lossy(&out.stdout).trim().to_string();
                path
            } else {
                String::new()
            }
        }
        Err(_) => String::new(),
    }
}

/**
 * Ensure that one or more commands exist in the system PATH.
 * If any command doesn't exist, an error is thrown.
 *
 * # Arguments
 *
 * * `commands` - The command(s) to check, comma-separated for multiple commands
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating all commands exist
 * * `Err(FsError)` - An error if any command doesn't exist
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::cmd_ensure_exists;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     // Check if a single command exists
 *     let result = cmd_ensure_exists("ls")?;
 *
 *     // Check if multiple commands exist
 *     let result = cmd_ensure_exists("ls,cat,grep")?;
 *
 *     Ok(())
 * }
 * ```
 */
pub fn cmd_ensure_exists(commands: &str) -> Result<String, FsError> {
    // Split the input by commas to handle multiple commands
    let command_list: Vec<&str> = commands
        .split(',')
        .map(|s| s.trim())
        .filter(|s| !s.is_empty())
        .collect();

    if command_list.is_empty() {
        return Err(FsError::CommandFailed(
            "No commands specified to check".to_string(),
        ));
    }

    let mut missing_commands = Vec::new();

    // Check each command
    for cmd in &command_list {
        let cmd_path = which(cmd);
        if cmd_path.is_empty() {
            missing_commands.push(cmd.to_string());
        }
    }

    // If any commands are missing, return an error
    if !missing_commands.is_empty() {
        return Err(FsError::CommandNotFound(missing_commands.join(", ")));
    }

    // All commands exist
    if command_list.len() == 1 {
        Ok(format!("Command '{}' exists", command_list[0]))
    } else {
        Ok(format!("All commands exist: {}", command_list.join(", ")))
    }
}

/// Make a file executable (equivalent to chmod +x).
///
/// # Arguments
///
/// * `path` - The path to the file to make executable
///
/// # Returns
///
/// * `Ok(String)` - A success message including the path
/// * `Err(FsError)` - An error if the operation failed
///
/// # Examples
///
/// ```no_run
/// use herosal_system::os::chmod_exec;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
///     // Make a file executable
///     chmod_exec("/path/to/file")?;
///     Ok(())
/// }
/// ```
pub fn chmod_exec(path: &str) -> Result<String, FsError> {
    let path_obj = Path::new(path);

    // Check if the path exists and is a file
    if !path_obj.exists() {
        return Err(FsError::FileNotFound(format!(
            "Path does not exist: {}",
            path
        )));
    }

    if !path_obj.is_file() {
        return Err(FsError::NotAFile(format!("Path is not a file: {}", path)));
    }

    // Get current permissions
    let metadata = fs::metadata(path).map_err(FsError::MetadataError)?;
    let mut permissions = metadata.permissions();

    // Set executable bit for user, group, and others
    #[cfg(unix)]
    {
        let mode = permissions.mode();
        // Add executable bit for user, group, and others (equivalent to +x)
        let new_mode = mode | 0o111;
        permissions.set_mode(new_mode);
    }

    #[cfg(not(unix))]
    {
        // On non-Unix platforms, we can't set executable bit directly
        // Just return success with a warning
        return Ok(format!(
            "Made {} executable (note: non-Unix platform, may not be fully supported)",
            path
        ));
    }

    // Apply the new permissions
    fs::set_permissions(path, permissions).map_err(|e| {
        FsError::CommandFailed(format!("Failed to set executable permissions: {}", e))
    })?;

    Ok(format!("Made {} executable", path))
}

/**
 * Compare the content of a file with an expected string.
 *
 * # Arguments
 *
 * * `path` - The path of the file to compare
 * * `expected` - The expected content to compare against
 *
 * # Returns
 *
 * * `bool` - True if the file content matches the expected string, false otherwise
 *
 * # Examples
 *
 * ```no_run
 * use herosal_os::compare;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     if compare("file.txt", "expected content")? {
 *         println!("File content matches!");
 *     } else {
 *         println!("File content differs");
 *     }
 *     Ok(())
 * }
 * ```
 */
pub fn compare(path: &str, expected: &str) -> Result<bool, FsError> {
    let content = file_read(path)?;
    Ok(content == expected)
}
