use anyhow::{bail, Result};
use std::process::Command;

/// Kubernetes Pod SSH enablement manager.
///
/// Enables SSH access within Kubernetes pods by:
/// - Checking if SSH server is installed (openssh-server)
/// - Generating SSH keys and storing them as Kubernetes secrets
/// - Setting up authorized_keys for pod SSH access
///
/// # Examples
///
/// ```no_run
/// use herolib_virt::kubernetes::KubeSsh;
///
/// let kubessh = KubeSsh::new("my-namespace", "my-pod");
///
/// // Enable SSH on the pod
/// kubessh.enable_ssh()?;
///
/// // Get the private key from the secret
/// let key = kubessh.get_private_key()?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub struct KubeSsh {
    namespace: String,
    pod_name: String,
    secret_name: String,
    key_name: String,
}

impl KubeSsh {
    /// Creates a new KubeSsh instance for a specific pod.
    ///
    /// # Arguments
    ///
    /// * `namespace` - Kubernetes namespace
    /// * `pod_name` - Pod name
    pub fn new<S: Into<String>>(namespace: S, pod_name: S) -> Self {
        let namespace = namespace.into();
        let pod_name = pod_name.into();
        let secret_name = format!("{}-ssh-keys", pod_name);

        Self {
            namespace,
            pod_name,
            secret_name,
            key_name: "develsshkey".to_string(),
        }
    }

    /// Sets a custom secret name (default: {pod-name}-ssh-keys).
    pub fn with_secret_name<S: Into<String>>(mut self, secret_name: S) -> Self {
        self.secret_name = secret_name.into();
        self
    }

    /// Sets a custom key name (default: develsshkey).
    pub fn with_key_name<S: Into<String>>(mut self, key_name: S) -> Self {
        self.key_name = key_name.into();
        self
    }

    /// Enables SSH on the Kubernetes pod.
    ///
    /// This function:
    /// 1. Verifies SSH is installed in the pod (openssh-server)
    /// 2. Creates an SSH key pair if not already in the secret
    /// 3. Stores the key pair as a Kubernetes secret
    /// 4. Configures authorized_keys in the pod
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - kubectl is not available
    /// - The pod is not accessible
    /// - SSH installation fails
    /// - Secret creation fails
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_virt::kubernetes::KubeSsh;
    ///
    /// let kubessh = KubeSsh::new("default", "my-pod");
    /// kubessh.enable_ssh()?;
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn enable_ssh(&self) -> Result<()> {
        // Step 1: Check if SSH is installed in the pod
        eprintln!("[KubeSsh] Step 1: Checking if SSH is installed in pod {}...", self.pod_name);
        self.ensure_ssh_installed()?;
        eprintln!("[KubeSsh] Step 1: SSH is installed!");

        // Step 2: Check if the secret already exists
        eprintln!("[KubeSsh] Step 2: Checking if secret '{}' exists...", self.secret_name);
        let secret_exists = self.secret_exists()?;
        eprintln!("[KubeSsh] Step 2: Secret exists: {}", secret_exists);

        // Step 3: Generate or retrieve SSH keys
        let (_private_key, public_key) = if secret_exists {
            eprintln!("[KubeSsh] Step 3: Retrieving SSH keys from existing secret...");
            let keys = self.get_keys_from_secret()?;
            eprintln!("[KubeSsh] Step 3: Keys retrieved from secret!");
            keys
        } else {
            eprintln!("[KubeSsh] Step 3: Generating new SSH keys...");
            let (priv_key, pub_key) = self.generate_ssh_keys()?;
            eprintln!("[KubeSsh] Step 3: Keys generated, storing in secret...");
            // Store in secret
            self.create_secret(&priv_key, &pub_key)?;
            eprintln!("[KubeSsh] Step 3: Secret created!");
            (priv_key, pub_key)
        };

        // Step 4: Configure authorized_keys in the pod
        eprintln!("[KubeSsh] Step 4: Setting up authorized_keys in pod...");
        self.setup_authorized_keys(&public_key)?;
        eprintln!("[KubeSsh] Step 4: authorized_keys configured!");

        Ok(())
    }

    /// Checks if SSH is installed in the pod and installs it if necessary.
    fn ensure_ssh_installed(&self) -> Result<()> {
        // Check if openssh-server is installed
        eprintln!("[KubeSsh]   Checking if sshd exists in pod...");
        let check_cmd = format!(
            "kubectl exec -n {} {} -- which sshd",
            self.namespace, self.pod_name
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&check_cmd)
            .output()?;

        if output.status.success() {
            // SSH is already installed
            eprintln!("[KubeSsh]   sshd already installed!");
            return Ok(());
        }

        // Install openssh-server
        eprintln!("[KubeSsh]   sshd not found, installing openssh-server (this may take a while)...");
        let install_cmd = format!(
            "kubectl exec -n {} {} -- sh -c 'apt-get update && apt-get install -y openssh-server openssh-client'",
            self.namespace, self.pod_name
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&install_cmd)
            .output()?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to install SSH in pod: {}", error);
        }

        eprintln!("[KubeSsh]   openssh-server installed successfully!");
        Ok(())
    }

    /// Checks if the SSH keys secret exists.
    fn secret_exists(&self) -> Result<bool> {
        let cmd = format!(
            "kubectl get secret {} -n {} --ignore-not-found",
            self.secret_name, self.namespace
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&cmd)
            .output()?;

        Ok(output.status.success() && !output.stdout.is_empty())
    }

    /// Generates a new SSH key pair using the SSH module.
    fn generate_ssh_keys(&self) -> Result<(String, String)> {
        use std::fs;
        use tempfile::TempDir;

        // Create a temporary directory for the keys
        let temp_dir = TempDir::new()?;
        let key_path = temp_dir.path().join("id_ed25519");

        // Generate SSH key using ssh-keygen
        let keygen_output = Command::new("ssh-keygen")
            .arg("-t")
            .arg("ed25519")
            .arg("-f")
            .arg(&key_path)
            .arg("-N")
            .arg("")
            .arg("-q")
            .output()?;

        if !keygen_output.status.success() {
            let error = String::from_utf8_lossy(&keygen_output.stderr);
            bail!("Failed to generate SSH key: {}", error);
        }

        // Read the generated keys
        let private_key = fs::read_to_string(&key_path)?;
        let public_key = fs::read_to_string(format!("{}.pub", key_path.display()))?;

        Ok((private_key, public_key))
    }

    /// Creates a Kubernetes secret with the SSH key pair.
    fn create_secret(&self, private_key: &str, public_key: &str) -> Result<()> {
        let cmd = format!(
            "kubectl create secret generic {} -n {} --from-literal={}='{}' --from-literal={}.pub='{}'",
            self.secret_name,
            self.namespace,
            self.key_name,
            private_key.replace("'", "\\'"),
            self.key_name,
            public_key.replace("'", "\\'")
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&cmd)
            .output()?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to create secret: {}", error);
        }

        Ok(())
    }

    /// Retrieves SSH keys from the existing secret.
    fn get_keys_from_secret(&self) -> Result<(String, String)> {
        // Get private key
        let priv_cmd = format!(
            "kubectl get secret {} -n {} -o jsonpath='{{.data.{}}}' | base64 -d",
            self.secret_name, self.namespace, self.key_name
        );

        let priv_output = Command::new("sh")
            .arg("-c")
            .arg(&priv_cmd)
            .output()?;

        if !priv_output.status.success() {
            bail!("Failed to retrieve private key from secret");
        }

        let private_key = String::from_utf8(priv_output.stdout)?;

        // Get public key
        let pub_cmd = format!(
            "kubectl get secret {} -n {} -o jsonpath='{{.data.{}.pub}}' | base64 -d",
            self.secret_name, self.namespace, self.key_name
        );

        let pub_output = Command::new("sh")
            .arg("-c")
            .arg(&pub_cmd)
            .output()?;

        if !pub_output.status.success() {
            bail!("Failed to retrieve public key from secret");
        }

        let public_key = String::from_utf8(pub_output.stdout)?;

        Ok((private_key, public_key))
    }

    /// Sets up authorized_keys in the pod using the public key.
    fn setup_authorized_keys(&self, public_key: &str) -> Result<()> {
        let setup_cmd = format!(
            "kubectl exec -n {} {} -- sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo \"{}\" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'",
            self.namespace,
            self.pod_name,
            public_key.trim().replace("'", "\\'")
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&setup_cmd)
            .output()?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to setup authorized_keys: {}", error);
        }

        Ok(())
    }

    /// Retrieves the private key from the secret.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_virt::kubernetes::KubeSsh;
    ///
    /// let kubessh = KubeSsh::new("default", "my-pod");
    /// let private_key = kubessh.get_private_key()?;
    /// println!("Private key:\n{}", private_key);
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn get_private_key(&self) -> Result<String> {
        let cmd = format!(
            "kubectl get secret {} -n {} -o jsonpath='{{.data.{}}}' | base64 -d",
            self.secret_name, self.namespace, self.key_name
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&cmd)
            .output()?;

        if !output.status.success() {
            bail!("Failed to retrieve private key from secret");
        }

        Ok(String::from_utf8(output.stdout)?)
    }

    /// Retrieves the public key from the secret.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_virt::kubernetes::KubeSsh;
    ///
    /// let kubessh = KubeSsh::new("default", "my-pod");
    /// let public_key = kubessh.get_public_key()?;
    /// println!("Public key:\n{}", public_key);
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn get_public_key(&self) -> Result<String> {
        let cmd = format!(
            "kubectl get secret {} -n {} -o jsonpath='{{.data.{}.pub}}' | base64 -d",
            self.secret_name, self.namespace, self.key_name
        );

        let output = Command::new("sh")
            .arg("-c")
            .arg(&cmd)
            .output()?;

        if !output.status.success() {
            bail!("Failed to retrieve public key from secret");
        }

        Ok(String::from_utf8(output.stdout)?)
    }

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

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

    /// Returns the secret name.
    pub fn secret_name(&self) -> &str {
        &self.secret_name
    }

    /// Returns the key name.
    pub fn key_name(&self) -> &str {
        &self.key_name
    }
}

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

    #[test]
    fn test_kubessh_creation() {
        let kubessh = KubeSsh::new("default", "test-pod");
        assert_eq!(kubessh.namespace(), "default");
        assert_eq!(kubessh.pod_name(), "test-pod");
        assert_eq!(kubessh.secret_name(), "test-pod-ssh-keys");
        assert_eq!(kubessh.key_name(), "develsshkey");
    }

    #[test]
    fn test_kubessh_custom_names() {
        let kubessh = KubeSsh::new("custom-ns", "custom-pod")
            .with_secret_name("my-secret")
            .with_key_name("custom-key");

        assert_eq!(kubessh.namespace(), "custom-ns");
        assert_eq!(kubessh.pod_name(), "custom-pod");
        assert_eq!(kubessh.secret_name(), "my-secret");
        assert_eq!(kubessh.key_name(), "custom-key");
    }
}
