use std::io::Read;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::time::Duration;

use anyhow::Result;

/// SSH Connection that uses the system's SSH client.
/// Provides methods for executing commands and transferring files.
#[derive(Clone)]
pub struct SshConnection {
    host: String,
    port: u16,
    user: String,
    ssh_private_key_path: Option<PathBuf>,
    timeout: Duration,
}

impl SshConnection {
    /// Creates a new SSH connection builder.
    pub fn builder() -> SshConnectionBuilder {
        SshConnectionBuilder::new()
    }

    /// Returns the host address.
    pub fn host(&self) -> &str {
        &self.host
    }

    /// Returns the SSH port.
    pub fn port(&self) -> u16 {
        self.port
    }

    /// Returns the SSH user.
    pub fn user(&self) -> &str {
        &self.user
    }

    /// Returns a reference to the identity file if set.
    pub fn ssh_private_key_path(&self) -> Option<&PathBuf> {
        self.ssh_private_key_path.as_ref()
    }

    /// Returns the connection timeout.
    pub fn timeout(&self) -> Duration {
        self.timeout
    }

    /// Builds SSH command arguments common to all operations.
    fn build_ssh_args(&self) -> Vec<String> {
        let mut args = Vec::new();

        // Add SSH options
        args.push("-o".to_string());
        args.push(format!("ConnectTimeout={}", self.timeout.as_secs()));

        // Don't check host key to avoid prompts
        args.push("-o".to_string());
        args.push("StrictHostKeyChecking=no".to_string());

        // Batch mode to avoid password prompts
        args.push("-o".to_string());
        args.push("BatchMode=yes".to_string());

        // Specify port if not default
        if self.port != 22 {
            args.push("-p".to_string());
            args.push(self.port.to_string());
        }

        // Add identity file if provided
        if let Some(identity) = &self.ssh_private_key_path {
            args.push("-i".to_string());
            args.push(identity.to_string_lossy().to_string());
        }

        args
    }

    /// Executes a command over SSH and returns its exit code and output.
    pub fn execute(&self, command: &str) -> Result<(i32, String)> {
        let mut args = self.build_ssh_args();

        // Add user and host
        args.push(format!("{}@{}", self.user, self.host));

        // Add the command to execute
        args.push(command.to_string());

        // Run the SSH command
        let mut child = Command::new("ssh")
            .args(&args)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()?;

        // Collect stdout and stderr
        let mut output = String::new();
        if let Some(mut stdout) = child.stdout.take() {
            stdout.read_to_string(&mut output)?;
        }

        let mut error_output = String::new();
        if let Some(mut stderr) = child.stderr.take() {
            stderr.read_to_string(&mut error_output)?;
        }

        // If there's error output, append it to the regular output
        if !error_output.is_empty() {
            if !output.is_empty() {
                output.push('\n');
            }
            output.push_str(&error_output);
        }

        // Wait for the command to complete and get exit status
        let status = child.wait()?;
        let code = status.code().unwrap_or(-1);

        Ok((code, output))
    }

    /// Checks if the host is reachable via SSH.
    pub fn ping(&self) -> Result<bool> {
        let result = self.execute("echo 'Connection successful'")?;
        Ok(result.0 == 0)
    }

    /// Creates a file transfer builder for this connection.
    pub fn file_transfer(&self) -> FileTransferBuilder {
        FileTransferBuilder::new(self.clone_connection_info())
    }

    /// Clones the connection configuration to create a new independent connection.
    pub fn clone_connection(&self) -> SshConnection {
        SshConnection {
            host: self.host.clone(),
            port: self.port,
            user: self.user.clone(),
            ssh_private_key_path: self.ssh_private_key_path.clone(),
            timeout: self.timeout,
        }
    }

    /// Internal method to clone connection info for file operations.
    fn clone_connection_info(&self) -> (String, u16, String, Option<PathBuf>, Duration) {
        (
            self.host.clone(),
            self.port,
            self.user.clone(),
            self.ssh_private_key_path.clone(),
            self.timeout,
        )
    }
}

/// Builder for configuring file transfers over SSH (rsync-based).
#[derive(Clone)]
pub struct FileTransferBuilder {
    host: String,
    port: u16,
    user: String,
    ssh_private_key_path: Option<PathBuf>,
    timeout: Duration,
    archive: bool,
    verbose: bool,
    compress: bool,
    delete: bool,
    exclude_patterns: Vec<String>,
}

impl FileTransferBuilder {
    /// Creates a new file transfer builder from connection info.
    fn new(
        (host, port, user, ssh_private_key_path, timeout): (
            String,
            u16,
            String,
            Option<PathBuf>,
            Duration,
        ),
    ) -> Self {
        Self {
            host,
            port,
            user,
            ssh_private_key_path,
            timeout,
            archive: true,
            verbose: true,
            compress: true,
            delete: false,
            exclude_patterns: Vec::new(),
        }
    }

    /// Enables archive mode (-a flag). Default: enabled.
    pub fn archive(mut self, enable: bool) -> Self {
        self.archive = enable;
        self
    }

    /// Enables verbose output (-v flag). Default: enabled.
    pub fn verbose(mut self, enable: bool) -> Self {
        self.verbose = enable;
        self
    }

    /// Enables compression (-z flag). Default: enabled.
    pub fn compress(mut self, enable: bool) -> Self {
        self.compress = enable;
        self
    }

    /// Enables deletion of files in destination that don't exist in source (--delete flag).
    pub fn delete(mut self, enable: bool) -> Self {
        self.delete = enable;
        self
    }

    /// Adds an exclude pattern (e.g., "*.log", ".git").
    pub fn exclude<S: Into<String>>(mut self, pattern: S) -> Self {
        self.exclude_patterns.push(pattern.into());
        self
    }

    /// Adds multiple exclude patterns.
    pub fn exclude_patterns<I, S>(mut self, patterns: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        for pattern in patterns {
            self.exclude_patterns.push(pattern.into());
        }
        self
    }

    /// Performs a push (upload) operation from local to remote.
    pub fn push<S: Into<String>>(&self, local_path: S, remote_path: S) -> Result<(i32, String)> {
        let local_path = local_path.into();
        let remote_path = remote_path.into();

        let rsync_cmd = self.build_rsync_command(
            &format!("{}/", local_path),
            &format!("{}@{}:{}/", self.user, self.host, remote_path),
        )?;

        let output = Command::new("rsync").args(&rsync_cmd).output()?;

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
        let combined = if stderr.is_empty() {
            stdout
        } else {
            format!("{}\n{}", stdout, stderr)
        };

        let code = output.status.code().unwrap_or(-1);
        Ok((code, combined))
    }

    /// Performs a pull (download) operation from remote to local.
    pub fn pull<S: Into<String>>(&self, remote_path: S, local_path: S) -> Result<(i32, String)> {
        let remote_path = remote_path.into();
        let local_path = local_path.into();

        let rsync_cmd = self.build_rsync_command(
            &format!("{}@{}:{}/", self.user, self.host, remote_path),
            &format!("{}/", local_path),
        )?;

        let output = Command::new("rsync").args(&rsync_cmd).output()?;

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
        let combined = if stderr.is_empty() {
            stdout
        } else {
            format!("{}\n{}", stdout, stderr)
        };

        let code = output.status.code().unwrap_or(-1);
        Ok((code, combined))
    }

    /// Builds the rsync command arguments.
    fn build_rsync_command(&self, source: &str, dest: &str) -> Result<Vec<String>> {
        let mut args = Vec::new();

        // Add rsync options
        if self.archive {
            args.push("-a".to_string());
        }
        if self.verbose {
            args.push("-v".to_string());
        }
        if self.compress {
            args.push("-z".to_string());
        }
        if self.delete {
            args.push("--delete".to_string());
        }

        // Add exclude patterns
        for pattern in &self.exclude_patterns {
            args.push(format!("--exclude={}", pattern));
        }

        // Add SSH configuration
        let ssh_opts = self.build_ssh_options();
        args.push("-e".to_string());
        args.push(ssh_opts);

        // Add source and destination
        args.push(source.to_string());
        args.push(dest.to_string());

        Ok(args)
    }

    /// Builds SSH options string for rsync.
    fn build_ssh_options(&self) -> String {
        let mut ssh_cmd = "ssh".to_string();

        if self.port != 22 {
            ssh_cmd.push_str(&format!(" -p {}", self.port));
        }

        if let Some(identity) = &self.ssh_private_key_path {
            ssh_cmd.push_str(&format!(" -i {}", identity.display()));
        }

        ssh_cmd.push_str(" -o StrictHostKeyChecking=no");
        ssh_cmd.push_str(" -o BatchMode=yes");
        ssh_cmd.push_str(&format!(" -o ConnectTimeout={}", self.timeout.as_secs()));

        ssh_cmd
    }
}

/// Builder for SSH connections.
pub struct SshConnectionBuilder {
    host: String,
    port: u16,
    user: String,
    ssh_private_key_path: Option<PathBuf>,
    timeout: Duration,
}

impl Default for SshConnectionBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl SshConnectionBuilder {
    /// Creates a new SSH connection builder with default values.
    pub fn new() -> Self {
        Self {
            host: "localhost".to_string(),
            port: 22,
            user: "root".to_string(),
            ssh_private_key_path: None,
            timeout: Duration::from_secs(10),
        }
    }

    /// Sets the target host.
    pub fn host<S: Into<String>>(mut self, host: S) -> Self {
        self.host = host.into();
        self
    }

    /// Sets the SSH port (default: 22).
    pub fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    /// Sets the SSH user.
    pub fn user<S: Into<String>>(mut self, user: S) -> Self {
        self.user = user.into();
        self
    }

    /// Sets the path to the SSH identity file (private key).
    /// This is required for key-based authentication.
    pub fn ssh_private_key_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
        self.ssh_private_key_path = Some(path.into());
        self
    }

    /// Sets the connection timeout.
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Sets the connection timeout in seconds.
    pub fn timeout_secs(mut self, secs: u64) -> Self {
        self.timeout = Duration::from_secs(secs);
        self
    }

    /// Configures this builder to connect to a Kubernetes pod via kubectl exec.
    ///
    /// This sets up SSH connection parameters for accessing a pod in a Kubernetes cluster.
    /// The pod must have SSH server installed and configured.
    ///
    /// # Arguments
    ///
    /// * `namespace` - Kubernetes namespace
    /// * `pod_name` - Pod name
    ///
    /// # Returns
    ///
    /// A `PodConnectionConfig` that can be used to verify and manage SSH access to the pod.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_os::net::ssh::SshConnection;
    ///
    /// let conn = SshConnection::builder()
    ///     .user("ubuntu")
    ///     .pod("default", "my-pod")?
    ///     .build();
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn pod<S: Into<String>>(mut self, namespace: S, pod_name: S) -> Result<Self> {
        use crate::net::ssh::pod_connection::PodConnectionConfig;

        let namespace = namespace.into();
        let pod_name = pod_name.into();
        let pod_config = PodConnectionConfig::new(namespace.clone(), pod_name.clone());

        // Check if SSH is already enabled in the pod
        let ssh_enabled = pod_config.is_ssh_enabled()?;

        if ssh_enabled {
            // SSH is enabled, set host to pod connection string
            // This is a special marker that will be handled by the connection implementation
            self.host = format!("pod://{}/{}", namespace, pod_name);
            pod_config.mark_ssh_enabled()?;
        }

        Ok(self)
    }

    /// Builds the SSH connection.
    pub fn build(self) -> SshConnection {
        SshConnection {
            host: self.host,
            port: self.port,
            user: self.user,
            ssh_private_key_path: self.ssh_private_key_path,
            timeout: self.timeout,
        }
    }
}
