//! Remote execution implementations for buildah operations.
//!
//! This module provides different executors for running buildah commands:
//! - `LocalExecutor`: Runs buildah on the local system (default behavior)
//! - `KubectlExecutor`: Runs buildah on a Kubernetes pod via kubectl exec

use super::{BuildahError, execute_buildah_command, set_thread_local_debug};
use crate::process::CommandResult;
use std::fs;
use std::process::Command;
use std::sync::Arc;

/// Trait for executing buildah commands on different targets (local, SSH, kubectl, etc.)
///
/// Implement this trait to enable buildah operations on remote systems.
pub trait RemoteExecutor: Send + Sync {
    /// Execute a buildah command with the given arguments
    fn execute(&self, args: &[&str]) -> Result<CommandResult, BuildahError>;

    /// Execute a command with stdin input (for content operations)
    fn execute_with_stdin(
        &self,
        args: &[&str],
        stdin: &[u8],
    ) -> Result<CommandResult, BuildahError>;

    /// Copy a file to the remote system
    fn copy_to(&self, local_path: &str, remote_path: &str) -> Result<(), BuildahError>;

    /// Copy a file from the remote system
    fn copy_from(&self, remote_path: &str, local_path: &str) -> Result<(), BuildahError>;

    /// Check if buildah is available on the target
    fn check_buildah(&self) -> Result<bool, BuildahError> {
        match self.execute(&["--version"]) {
            Ok(result) => Ok(result.success),
            Err(_) => Ok(false),
        }
    }
}

/// Local executor - runs buildah commands on the local system.
///
/// This is the default executor that wraps the existing local execution behavior.
#[derive(Debug, Clone, Default)]
pub struct LocalExecutor {
    debug: bool,
}

impl LocalExecutor {
    /// Create a new local executor
    pub fn new() -> Self {
        Self { debug: false }
    }

    /// Create a new local executor with debug enabled
    pub fn with_debug(debug: bool) -> Self {
        Self { debug }
    }
}

impl RemoteExecutor for LocalExecutor {
    fn execute(&self, args: &[&str]) -> Result<CommandResult, BuildahError> {
        if self.debug {
            set_thread_local_debug(true);
        }
        let result = execute_buildah_command(args);
        if self.debug {
            set_thread_local_debug(false);
        }
        result
    }

    fn execute_with_stdin(
        &self,
        args: &[&str],
        stdin: &[u8],
    ) -> Result<CommandResult, BuildahError> {
        use std::io::Write;
        use std::process::Stdio;

        if self.debug {
            println!(
                "Executing buildah command with stdin: buildah {}",
                args.join(" ")
            );
        }

        let mut child = Command::new("buildah")
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(BuildahError::CommandExecutionFailed)?;

        if let Some(mut child_stdin) = child.stdin.take() {
            child_stdin
                .write_all(stdin)
                .map_err(|e| BuildahError::Other(format!("Failed to write stdin: {}", e)))?;
        }

        let output = child
            .wait_with_output()
            .map_err(BuildahError::CommandExecutionFailed)?;

        let result = CommandResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            success: output.status.success(),
            code: output.status.code().unwrap_or(-1),
        };

        if self.debug {
            if !result.stdout.is_empty() {
                println!("Command stdout: {}", result.stdout);
            }
            if !result.stderr.is_empty() {
                println!("Command stderr: {}", result.stderr);
            }
        }

        if result.success {
            Ok(result)
        } else {
            Err(BuildahError::CommandFailed(format!(
                "Command failed with code {}: {}",
                result.code,
                result.stderr.trim()
            )))
        }
    }

    fn copy_to(&self, local_path: &str, remote_path: &str) -> Result<(), BuildahError> {
        // Local executor: just copy the file
        fs::copy(local_path, remote_path)
            .map_err(|e| BuildahError::Other(format!("Failed to copy file: {}", e)))?;
        Ok(())
    }

    fn copy_from(&self, remote_path: &str, local_path: &str) -> Result<(), BuildahError> {
        // Local executor: just copy the file
        fs::copy(remote_path, local_path)
            .map_err(|e| BuildahError::Other(format!("Failed to copy file: {}", e)))?;
        Ok(())
    }
}

/// Type alias for a boxed executor
pub type BoxedExecutor = Arc<dyn RemoteExecutor>;

/// Create a default local executor
pub fn default_executor() -> BoxedExecutor {
    Arc::new(LocalExecutor::new())
}

/// Kubectl executor - runs buildah commands in a Kubernetes pod via kubectl exec.
///
/// This executor allows running buildah commands on a remote Kubernetes pod,
/// enabling container image builds inside Kubernetes clusters.
///
/// # Example
///
/// ```rust,no_run
/// use herolib_virt::buildah::{Builder, KubectlExecutor};
///
/// // Create an executor for an existing pod with buildah installed
/// let executor = KubectlExecutor::new("my-namespace", "buildah-pod");
///
/// // Use with Builder for remote image building
/// let builder = Builder::with_executor("my-container", "alpine:latest", executor)?;
/// builder.run("apk add curl")?;
/// builder.commit("my-image:latest")?;
/// # Ok::<(), herolib_virt::buildah::BuildahError>(())
/// ```
#[derive(Debug, Clone)]
pub struct KubectlExecutor {
    /// Kubernetes namespace
    namespace: String,
    /// Pod name
    pod_name: String,
    /// Container name (optional, uses default if not specified)
    container: Option<String>,
    /// Debug mode
    debug: bool,
}

impl KubectlExecutor {
    /// Create a new kubectl executor for a Kubernetes pod
    ///
    /// # Arguments
    ///
    /// * `namespace` - Kubernetes namespace where the pod is running
    /// * `pod_name` - Name of the pod to execute commands in
    pub fn new(namespace: impl Into<String>, pod_name: impl Into<String>) -> Self {
        Self {
            namespace: namespace.into(),
            pod_name: pod_name.into(),
            container: None,
            debug: false,
        }
    }

    /// Set the container name within the pod
    ///
    /// Use this when the pod has multiple containers and you need to
    /// specify which one to execute commands in.
    pub fn container(mut self, container: impl Into<String>) -> Self {
        self.container = Some(container.into());
        self
    }

    /// Enable or disable debug mode
    ///
    /// When enabled, prints kubectl commands and their output.
    pub fn debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }

    /// Get the namespace
    pub fn namespace(&self) -> &str {
        &self.namespace
    }

    /// Get the pod name
    pub fn pod_name(&self) -> &str {
        &self.pod_name
    }

    /// Execute any command in the pod (not just buildah)
    ///
    /// This is useful for running arbitrary commands in the pod,
    /// such as installing packages or checking file contents.
    pub fn exec_raw(&self, command: &[&str]) -> Result<CommandResult, BuildahError> {
        let mut args = vec![
            "exec".to_string(),
            "-n".to_string(),
            self.namespace.clone(),
            self.pod_name.clone(),
        ];

        if let Some(container) = &self.container {
            args.push("-c".to_string());
            args.push(container.clone());
        }

        args.push("--".to_string());
        args.extend(command.iter().map(|s| s.to_string()));

        if self.debug {
            println!("[kubectl] Executing: kubectl {}", args.join(" "));
        }

        let output = Command::new("kubectl")
            .args(&args)
            .output()
            .map_err(|e| BuildahError::Other(format!("Failed to execute kubectl: {}", e)))?;

        let result = CommandResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            success: output.status.success(),
            code: output.status.code().unwrap_or(-1),
        };

        if self.debug {
            if !result.stdout.is_empty() {
                println!("[kubectl] stdout: {}", result.stdout);
            }
            if !result.stderr.is_empty() {
                println!("[kubectl] stderr: {}", result.stderr);
            }
        }

        Ok(result)
    }

    /// Execute a buildah command in the pod
    ///
    /// This is a convenience method that prepends "buildah" to the arguments.
    pub fn buildah(&self, args: &[&str]) -> Result<CommandResult, BuildahError> {
        let mut cmd = vec!["buildah"];
        cmd.extend(args);
        self.exec_raw(&cmd)
    }
}

impl RemoteExecutor for KubectlExecutor {
    fn execute(&self, args: &[&str]) -> Result<CommandResult, BuildahError> {
        let mut cmd = vec!["buildah"];
        cmd.extend(args);

        let result = self.exec_raw(&cmd)?;

        // If command failed, return CommandFailed error so Builder can handle it
        if !result.success {
            return Err(BuildahError::CommandFailed(format!(
                "Command failed with code {}: {}",
                result.code,
                result.stderr.trim()
            )));
        }

        Ok(result)
    }

    fn execute_with_stdin(
        &self,
        args: &[&str],
        stdin: &[u8],
    ) -> Result<CommandResult, BuildahError> {
        use std::io::Write;
        use std::process::Stdio;

        let mut kubectl_args = vec![
            "exec".to_string(),
            "-i".to_string(), // Enable stdin
            "-n".to_string(),
            self.namespace.clone(),
            self.pod_name.clone(),
        ];

        if let Some(container) = &self.container {
            kubectl_args.push("-c".to_string());
            kubectl_args.push(container.clone());
        }

        kubectl_args.push("--".to_string());
        kubectl_args.push("buildah".to_string());
        kubectl_args.extend(args.iter().map(|s| s.to_string()));

        if self.debug {
            println!(
                "[kubectl] Executing with stdin: kubectl {}",
                kubectl_args.join(" ")
            );
        }

        let mut child = Command::new("kubectl")
            .args(&kubectl_args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(|e| BuildahError::Other(format!("Failed to spawn kubectl: {}", e)))?;

        if let Some(mut child_stdin) = child.stdin.take() {
            child_stdin
                .write_all(stdin)
                .map_err(|e| BuildahError::Other(format!("Failed to write stdin: {}", e)))?;
        }

        let output = child
            .wait_with_output()
            .map_err(|e| BuildahError::Other(format!("Failed to wait for kubectl: {}", e)))?;

        let result = CommandResult {
            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
            success: output.status.success(),
            code: output.status.code().unwrap_or(-1),
        };

        if self.debug {
            if !result.stdout.is_empty() {
                println!("[kubectl] stdout: {}", result.stdout);
            }
            if !result.stderr.is_empty() {
                println!("[kubectl] stderr: {}", result.stderr);
            }
        }

        if result.success {
            Ok(result)
        } else {
            Err(BuildahError::CommandFailed(format!(
                "kubectl exec failed: {}",
                result.stderr
            )))
        }
    }

    fn copy_to(&self, local_path: &str, remote_path: &str) -> Result<(), BuildahError> {
        let dest = format!("{}/{}:{}", self.namespace, self.pod_name, remote_path);
        let mut args = vec!["cp", local_path, &dest];

        if let Some(container) = &self.container {
            args.push("-c");
            args.push(container);
        }

        if self.debug {
            println!("[kubectl] Executing: kubectl {}", args.join(" "));
        }

        let output = Command::new("kubectl")
            .args(&args)
            .output()
            .map_err(|e| BuildahError::Other(format!("Failed to execute kubectl cp: {}", e)))?;

        if output.status.success() {
            Ok(())
        } else {
            Err(BuildahError::Other(format!(
                "kubectl cp failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )))
        }
    }

    fn copy_from(&self, remote_path: &str, local_path: &str) -> Result<(), BuildahError> {
        let src = format!("{}/{}:{}", self.namespace, self.pod_name, remote_path);
        let mut args = vec!["cp", &src, local_path];

        if let Some(container) = &self.container {
            args.push("-c");
            args.push(container);
        }

        if self.debug {
            println!("[kubectl] Executing: kubectl {}", args.join(" "));
        }

        let output = Command::new("kubectl")
            .args(&args)
            .output()
            .map_err(|e| BuildahError::Other(format!("Failed to execute kubectl cp: {}", e)))?;

        if output.status.success() {
            Ok(())
        } else {
            Err(BuildahError::Other(format!(
                "kubectl cp failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )))
        }
    }
}

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

    #[test]
    fn test_local_executor_creation() {
        let executor = LocalExecutor::new();
        assert!(!executor.debug);

        let executor_debug = LocalExecutor::with_debug(true);
        assert!(executor_debug.debug);
    }

    #[test]
    fn test_kubectl_executor_builder() {
        let executor = KubectlExecutor::new("my-ns", "my-pod")
            .container("builder")
            .debug(true);

        assert_eq!(executor.namespace(), "my-ns");
        assert_eq!(executor.pod_name(), "my-pod");
        assert_eq!(executor.container, Some("builder".to_string()));
        assert!(executor.debug);
    }
}
