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,
}

/// Builder for killing processes with flexible options
///
/// This builder pattern allows you to kill processes either by pattern or by PID,
/// with optional recursive killing of child processes.
///
/// # Examples
///
/// ```no_run
/// use herolib_os::process::ProcessKiller;
///
/// // Kill by pattern
/// ProcessKiller::new().pattern("server").kill()?;
///
/// // Kill by PID with children
/// ProcessKiller::new().pid(1234).recursive(true).kill()?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Debug)]
pub struct ProcessKiller {
    pattern: Option<String>,
    pid: Option<i64>,
    recursive: bool,
}

impl ProcessKiller {
    /// Create a new process killer builder
    pub fn new() -> Self {
        Self {
            pattern: None,
            pid: None,
            recursive: false,
        }
    }

    /// Set the process pattern to match
    ///
    /// Matches processes by name/pattern.
    pub fn pattern(mut self, pattern: &str) -> Self {
        self.pattern = Some(pattern.to_string());
        self
    }

    /// Set the process ID to kill
    ///
    /// Kills the process with this specific PID.
    pub fn pid(mut self, pid: i64) -> Self {
        self.pid = Some(pid);
        self
    }

    /// Enable recursive killing of child processes
    ///
    /// When true, kills the process and all its children.
    pub fn recursive(mut self, recursive: bool) -> Self {
        self.recursive = recursive;
        self
    }

    /// Execute the kill operation
    ///
    /// # Returns
    ///
    /// * `Ok(String)` - Success message
    /// * `Err(ProcessError)` - If the operation failed
    pub fn kill(self) -> Result<String, ProcessError> {
        if self.pattern.is_some() && self.pid.is_some() {
            return Err(ProcessError::CommandFailed(
                "Cannot specify both pattern and PID".to_string(),
            ));
        }

        if let Some(ref pattern) = self.pattern {
            self.kill_by_pattern(pattern)
        } else if let Some(pid) = self.pid {
            if self.recursive {
                self.kill_recursive_by_pid(pid)
            } else {
                self.kill_single_pid(pid)
            }
        } else {
            Err(ProcessError::CommandFailed(
                "Must specify either pattern or PID".to_string(),
            ))
        }
    }

    fn kill_by_pattern(&self, 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
                )))
            }
        }
    }

    fn kill_single_pid(&self, pid: i64) -> Result<String, ProcessError> {
        if pid <= 0 {
            return Err(ProcessError::CommandFailed(format!(
                "Invalid process ID: {}",
                pid
            )));
        }

        #[cfg(target_os = "windows")]
        {
            let output = Command::new("taskkill")
                .arg("/F")
                .arg("/PID")
                .arg(pid.to_string())
                .output()
                .map_err(ProcessError::CommandExecutionFailed)?;

            if output.status.success() {
                Ok(format!("Process {} killed", pid))
            } else {
                Err(ProcessError::CommandFailed(format!(
                    "Failed to kill process {}",
                    pid
                )))
            }
        }

        #[cfg(any(target_os = "macos", target_os = "linux"))]
        {
            let output = Command::new("kill")
                .arg("-9")
                .arg(pid.to_string())
                .output()
                .map_err(ProcessError::CommandExecutionFailed)?;

            if output.status.success() {
                Ok(format!("Process {} killed", pid))
            } else {
                Err(ProcessError::CommandFailed(format!(
                    "Failed to kill process {}",
                    pid
                )))
            }
        }
    }

    fn kill_recursive_by_pid(&self, pid: i64) -> Result<String, ProcessError> {
        if pid <= 0 {
            return Err(ProcessError::CommandFailed(format!(
                "Invalid process ID: {}",
                pid
            )));
        }

        #[cfg(target_os = "macos")]
        {
            // macOS: Use recursive kill
            // First get all child processes
            let output = Command::new("pgrep")
                .arg("-P")
                .arg(pid.to_string())
                .output()
                .map_err(ProcessError::CommandExecutionFailed)?;

            // Kill all child processes first
            let children = String::from_utf8_lossy(&output.stdout);
            for child_pid in children.lines() {
                let _ = Command::new("kill")
                    .arg("-9")
                    .arg(child_pid.trim())
                    .output();
            }

            // Kill the parent process
            let output = Command::new("kill")
                .arg("-9")
                .arg(pid.to_string())
                .output()
                .map_err(ProcessError::CommandExecutionFailed)?;

            if output.status.success() {
                Ok(format!("Process {} and children killed", pid))
            } else {
                Err(ProcessError::CommandFailed(format!(
                    "Failed to kill process {}",
                    pid
                )))
            }
        }

        #[cfg(target_os = "linux")]
        {
            // Linux: Use pkill with tree killing
            let output = Command::new("pkill")
                .arg("-9")
                .arg("-P")
                .arg(pid.to_string())
                .output();

            // Try pkill approach
            match output {
                Ok(result) => {
                    if !result.status.success() {
                        // Even if pkill fails, try regular kill
                        let kill_output = Command::new("kill")
                            .arg("-9")
                            .arg(pid.to_string())
                            .output()
                            .map_err(ProcessError::CommandExecutionFailed)?;

                        if kill_output.status.success() {
                            Ok(format!("Process {} and children killed", pid))
                        } else {
                            Err(ProcessError::CommandFailed(format!(
                                "Failed to kill process {}",
                                pid
                            )))
                        }
                    } else {
                        Ok(format!("Process {} and children killed", pid))
                    }
                }
                Err(e) => {
                    // Fallback to kill command
                    let kill_output = Command::new("kill")
                        .arg("-9")
                        .arg(pid.to_string())
                        .output()
                        .map_err(ProcessError::CommandExecutionFailed)?;

                    if kill_output.status.success() {
                        Ok(format!("Process {} and children killed", pid))
                    } else {
                        Err(ProcessError::CommandFailed(format!(
                            "Failed to kill process {}",
                            pid
                        )))
                    }
                }
            }
        }

        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
        {
            Err(ProcessError::CommandFailed(
                "Process killing not supported on this platform".to_string(),
            ))
        }
    }
}

/**
 * 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 herolib_os::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 herolib_os::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(),
        )),
    }
}
