use std::error::Error;
use std::fmt;
use std::io;
use std::process::Command;

/// Error type for process management operations
///
/// This enum represents various errors that can occur during process management
/// operations such as listing, finding, or killing processes.
#[derive(Debug)]
pub enum ProcessError {
    /// An error occurred while executing a command
    CommandExecutionFailed(io::Error),
    /// A command executed successfully but returned an error
    CommandFailed(String),
    /// No process was found matching the specified pattern
    NoProcessFound(String),
    /// Multiple processes were found matching the specified pattern
    MultipleProcessesFound(String, usize),
}

/// Implement Display for ProcessError to provide human-readable error messages
impl fmt::Display for ProcessError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ProcessError::CommandExecutionFailed(e) => {
                write!(f, "Failed to execute command: {}", e)
            }
            ProcessError::CommandFailed(e) => write!(f, "{}", e),
            ProcessError::NoProcessFound(pattern) => {
                write!(f, "No processes found matching '{}'", pattern)
            }
            ProcessError::MultipleProcessesFound(pattern, count) => write!(
                f,
                "Multiple processes ({}) found matching '{}'",
                count, pattern
            ),
        }
    }
}

// Implement Error trait for ProcessError
impl Error for ProcessError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            ProcessError::CommandExecutionFailed(e) => Some(e),
            _ => None,
        }
    }
}

// Define a struct to represent process information
#[derive(Debug, Clone)]
pub struct ProcessInfo {
    pub pid: i64,
    pub name: String,
    pub memory: f64,
    pub cpu: f64,
}

/**
 * Check if a command exists in PATH.
 *
 * # Arguments
 *
 * * `cmd` - The command to check
 *
 * # Returns
 *
 * * `Option<String>` - The full path to the command if found, None otherwise
 *
 * # Examples
 *
 * ```
 * use herosal_process::which;
 *
 * match which("git") {
 *     Some(path) => println!("Git is installed at: {}", path),
 *     None => println!("Git is not installed"),
 * }
 * ```
 */
pub fn which(cmd: &str) -> Option<String> {
    #[cfg(target_os = "windows")]
    let which_cmd = "where";

    #[cfg(any(target_os = "macos", target_os = "linux"))]
    let which_cmd = "which";

    let output = Command::new(which_cmd).arg(cmd).output();

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

/**
 * Kill processes matching a pattern.
 *
 * # Arguments
 *
 * * `pattern` - The pattern to match against process names
 *
 * # Returns
 *
 * * `Ok(String)` - A success message indicating processes were killed or none were found
 * * `Err(ProcessError)` - An error if the kill operation failed
 *
 * # Examples
 *
 * ```
 * // Kill all processes with "server" in their name
 * use herosal_process::kill;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let result = kill("server")?;
 *     println!("{}", result);
 *     Ok(())
 * }
 * ```
 */
pub fn kill(pattern: &str) -> Result<String, ProcessError> {
    // Platform specific implementation
    #[cfg(target_os = "windows")]
    {
        // On Windows, use taskkill with wildcard support
        let mut args = vec!["/F"]; // Force kill

        if pattern.contains('*') {
            // If it contains wildcards, use filter
            args.extend(&["/FI", &format!("IMAGENAME eq {}", pattern)]);
        } else {
            // Otherwise use image name directly
            args.extend(&["/IM", pattern]);
        }

        let output = Command::new("taskkill")
            .args(&args)
            .output()
            .map_err(ProcessError::CommandExecutionFailed)?;

        if output.status.success() {
            Ok("Successfully killed processes".to_string())
        } else {
            let error = String::from_utf8_lossy(&output.stderr);
            if error.is_empty() {
                let stdout = String::from_utf8_lossy(&output.stdout);
                if stdout.contains("No tasks") {
                    Ok("No matching processes found".to_string())
                } else {
                    Err(ProcessError::CommandFailed(format!(
                        "Failed to kill processes: {}",
                        stdout
                    )))
                }
            } else {
                Err(ProcessError::CommandFailed(format!(
                    "Failed to kill processes: {}",
                    error
                )))
            }
        }
    }

    #[cfg(any(target_os = "macos", target_os = "linux"))]
    {
        // On Unix-like systems, use pkill which has built-in pattern matching
        let output = Command::new("pkill")
            .arg("-f") // Match against full process name/args
            .arg(pattern)
            .output()
            .map_err(ProcessError::CommandExecutionFailed)?;

        // pkill returns 0 if processes were killed, 1 if none matched
        if output.status.success() {
            Ok("Successfully killed processes".to_string())
        } else if output.status.code() == Some(1) {
            Ok("No matching processes found".to_string())
        } else {
            let error = String::from_utf8_lossy(&output.stderr);
            Err(ProcessError::CommandFailed(format!(
                "Failed to kill processes: {}",
                error
            )))
        }
    }
}

/**
 * List processes matching a pattern (or all if pattern is empty).
 *
 * # Arguments
 *
 * * `pattern` - The pattern to match against process names (empty string for all processes)
 *
 * # Returns
 *
 * * `Ok(Vec<ProcessInfo>)` - A vector of process information for matching processes
 * * `Err(ProcessError)` - An error if the list operation failed
 *
 * # Examples
 *
 * ```
 * // List all processes
 * use herosal_process::process_list;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let processes = process_list("")?;
 *
 *     // List processes with "server" in their name
 *     let processes = process_list("server")?;
 *     for proc in processes {
 *         println!("PID: {}, Name: {}", proc.pid, proc.name);
 *     }
 *     Ok(())
 * }
 * ```
 */
pub fn process_list(pattern: &str) -> Result<Vec<ProcessInfo>, ProcessError> {
    let mut processes = Vec::new();

    // Platform specific implementations
    #[cfg(target_os = "windows")]
    {
        // Windows implementation using wmic
        let output = Command::new("wmic")
            .args(&["process", "list", "brief"])
            .output()
            .map_err(ProcessError::CommandExecutionFailed)?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();

            // Parse output (assuming format: Handle Name Priority)
            for line in stdout.lines().skip(1) {
                // Skip header
                let parts: Vec<&str> = line.trim().split_whitespace().collect();
                if parts.len() >= 2 {
                    let pid = parts[0].parse::<i64>().unwrap_or(0);
                    let name = parts[1].to_string();

                    // Filter by pattern if provided
                    if !pattern.is_empty() && !name.contains(pattern) {
                        continue;
                    }

                    processes.push(ProcessInfo {
                        pid,
                        name,
                        memory: 0.0, // Placeholder
                        cpu: 0.0,    // Placeholder
                    });
                }
            }
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
            return Err(ProcessError::CommandFailed(format!(
                "Failed to list processes: {}",
                stderr
            )));
        }
    }

    #[cfg(any(target_os = "macos", target_os = "linux"))]
    {
        // Unix implementation using ps
        let output = Command::new("ps")
            .args(&["-eo", "pid,comm"])
            .output()
            .map_err(ProcessError::CommandExecutionFailed)?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();

            // Parse output (assuming format: PID COMMAND)
            for line in stdout.lines().skip(1) {
                // Skip header
                let parts: Vec<&str> = line.trim().split_whitespace().collect();
                if parts.len() >= 2 {
                    let pid = parts[0].parse::<i64>().unwrap_or(0);
                    let name = parts[1].to_string();

                    // Filter by pattern if provided
                    if !pattern.is_empty() && !name.contains(pattern) {
                        continue;
                    }

                    processes.push(ProcessInfo {
                        pid,
                        name,
                        memory: 0.0, // Placeholder
                        cpu: 0.0,    // Placeholder
                    });
                }
            }
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
            return Err(ProcessError::CommandFailed(format!(
                "Failed to list processes: {}",
                stderr
            )));
        }
    }

    Ok(processes)
}

/**
 * Get a single process matching the pattern (error if 0 or more than 1 match).
 *
 * # Arguments
 *
 * * `pattern` - The pattern to match against process names
 *
 * # Returns
 *
 * * `Ok(ProcessInfo)` - Information about the matching process
 * * `Err(ProcessError)` - An error if no process or multiple processes match
 *
 * # Examples
 *
 * ```no_run
 * use herosal_process::process_get;
 *
 * fn main() -> Result<(), Box<dyn std::error::Error>> {
 *     let process = process_get("unique-server-name")?;
 *     println!("Found process: {} (PID: {})", process.name, process.pid);
 *     Ok(())
 * }
 * ```
 */
pub fn process_get(pattern: &str) -> Result<ProcessInfo, ProcessError> {
    let processes = process_list(pattern)?;

    match processes.len() {
        0 => Err(ProcessError::NoProcessFound(pattern.to_string())),
        1 => Ok(processes[0].clone()),
        _ => Err(ProcessError::MultipleProcessesFound(
            pattern.to_string(),
            processes.len(),
        )),
    }
}
