use super::{FilterOption, FilterPlugin};
use log::*;
use std::io::{Read, Result, Write};
use std::process::{Child, Command, Stdio};
use which::which;

/// A filter that executes an external program and pipes input through it.
///
/// This filter spawns an external command, pipes the input stream to its stdin,
/// and writes the stdout to the output stream. Supports async-like behavior via
/// threads for concurrent I/O. Requires the program to be available on PATH.
#[derive(Debug, Clone)]
pub struct ExecFilter {
    program: String,
    args: Vec<String>,
    supported: bool,
    split_whitespace: bool,
    child_process: Option<Child>,
    stdin_writer: Option<std::process::ChildStdin>,
    stdout_reader: Option<std::process::ChildStdout>,
}

impl ExecFilter {
    /// Creates a new `ExecFilter` for the specified program and arguments.
    ///
    /// Checks if the program is available using `which` and stores the resolved path.
    ///
    /// # Arguments
    ///
    /// * `program` - The name or path of the program to execute.
    /// * `args` - A slice of string slices representing the arguments to pass to the program.
    /// * `split_whitespace` - Whether to split arguments on whitespace when parsing (unused in this context).
    ///
    /// # Returns
    ///
    /// A new `ExecFilter` instance.
    ///
    /// # Examples
    ///
    /// ```
    /// use keep::filter_plugin::exec::ExecFilter;
    ///
    /// let filter = ExecFilter::new("grep", vec!["-i", "error"], false);
    /// assert!(filter.supported);
    /// ```
    pub fn new(program: &str, args: Vec<&str>, split_whitespace: bool) -> ExecFilter {
        let program_path = which(program);
        let supported = program_path.is_ok();

        ExecFilter {
            program: program_path
                .map_or_else(|| program.to_string(), |p| p.to_string_lossy().to_string()),
            args: args.iter().map(|s| s.to_string()).collect(),
            supported,
            split_whitespace,
            child_process: None,
            stdin_writer: None,
            stdout_reader: None,
        }
    }
}

impl FilterPlugin for ExecFilter {
    /// Filters the input by piping it through the external program and writing the output.
    ///
    /// Spawns the process with piped I/O, uses threads for concurrent input/output
    /// copying, and waits for completion. Errors if the program isn't found or fails.
    ///
    /// # Arguments
    ///
    /// * `reader` - A boxed mutable reference to the input reader providing the data stream to pipe to the program.
    /// * `writer` - A boxed mutable reference to the output writer where the program's output is sent.
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` on success, or an `io::Error` if process spawning, piping, or execution fails.
    ///
    /// # Errors
    ///
    /// * NotFound - Program not available.
    /// * Other - Spawn, I/O, or wait failures.
    ///
    /// # Examples
    ///
    /// ```
    /// use keep::filter_plugin::exec::ExecFilter;
    /// use std::io::{Read, Write};
    ///
    /// let mut filter = ExecFilter::new("cat", vec![], false);
    /// // In filter context:
    /// filter.filter(Box::new(&mut input), Box::new(&mut output)).unwrap();
    /// ```
    fn filter(&mut self, reader: &mut dyn Read, writer: &mut dyn Write) -> Result<()> {
        if !self.supported {
            return Err(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("Program '{}' not found", self.program),
            ));
        }

        debug!(
            "FILTER_EXEC: Executing command: {} {:?}",
            self.program, self.args
        );

        // Read all input first
        let mut input_data = Vec::new();
        std::io::copy(reader, &mut input_data)?;

        let mut child = Command::new(&self.program)
            .args(&self.args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .map_err(|e| {
                std::io::Error::new(
                    std::io::ErrorKind::Other,
                    format!("Failed to spawn process '{}': {}", self.program, e),
                )
            })?;

        let mut stdin = child.stdin.take().ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                "Failed to capture stdin from child process",
            )
        })?;

        // Write input to child stdin
        stdin.write_all(&input_data)?;
        drop(stdin); // Close stdin to signal EOF

        let mut stdout = child.stdout.take().ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                "Failed to capture stdout from child process",
            )
        })?;

        // Copy stdout to writer
        std::io::copy(&mut stdout, writer)?;

        // Wait for the child process to finish
        let output = child.wait_with_output().map_err(|e| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Failed to wait on child process: {}", e),
            )
        })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            if !stderr.is_empty() {
                warn!("FILTER_EXEC: Process stderr: {}", stderr);
            }
            return Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Process exited with error: {:?}", output.status),
            ));
        }

        debug!("FILTER_EXEC: Process completed successfully");
        Ok(())
    }

    /// Clones this filter into a new boxed instance.
    ///
    /// Creates a new instance without active process handles.
    ///
    /// # Returns
    ///
    /// A new `Box<dyn FilterPlugin>` representing a clone of this filter.
    fn clone_box(&self) -> Box<dyn FilterPlugin> {
        Box::new(ExecFilter {
            program: self.program.clone(),
            args: self.args.clone(),
            supported: self.supported,
            split_whitespace: self.split_whitespace,
            child_process: None,
            stdin_writer: None,
            stdout_reader: None,
        })
    }

    /// Returns the configuration options for this filter.
    ///
    /// Defines "command" as required and "split_whitespace" as optional.
    ///
    /// # Returns
    ///
    /// A vector of `FilterOption` describing the filter's configurable parameters.
    fn options(&self) -> Vec<FilterOption> {
        vec![
            FilterOption {
                name: "command".to_string(),
                default: None,
                required: true,
            },
            FilterOption {
                name: "split_whitespace".to_string(),
                default: Some(serde_json::Value::Bool(true)),
                required: false,
            },
        ]
    }

    fn description(&self) -> &str {
        "Pipe input through an external command"
    }
}

// Register the plugin at module initialization time
#[ctor::ctor]
fn register_exec_filter() {
    crate::services::filter_service::register_filter_plugin("exec", || {
        // Create a dummy instance - actual creation happens in create method
        Box::new(ExecFilter {
            program: String::new(),
            args: Vec::new(),
            supported: false,
            split_whitespace: true,
            child_process: None,
            stdin_writer: None,
            stdout_reader: None,
        })
    })
    .expect("Failed to register exec filter");
}
