use anyhow::Result;
use std::path::{Path, PathBuf};
use std::process::Command;

/// SSH Key type enumeration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SshKeyType {
    /// RSA key type (4096-bit by default)
    Rsa,
    /// ED25519 key type (recommended)
    Ed25519,
}

impl SshKeyType {
    /// Returns the SSH key type flag string.
    pub fn as_str(&self) -> &'static str {
        match self {
            SshKeyType::Rsa => "rsa",
            SshKeyType::Ed25519 => "ed25519",
        }
    }
}

/// Builder for SSH key generation with flexible configuration.
///
/// Supports generating SSH keys with optional password protection using the builder pattern.
/// Defaults to ED25519 keys without a passphrase.
///
/// # Examples
///
/// ```no_run
/// use herolib_os::net::ssh::keys::{SshKeyGenerator, SshKeyType};
///
/// // Generate an ED25519 key without passphrase
/// let result = SshKeyGenerator::new("/tmp/my_key")
///     .key_type(SshKeyType::Ed25519)
///     .generate()?;
///
/// // Generate an RSA key with passphrase
/// let result = SshKeyGenerator::new("/tmp/secure_key")
///     .key_type(SshKeyType::Rsa)
///     .bits(4096)
///     .passphrase("my-secure-password")
///     .comment("user@host")
///     .generate()?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub struct SshKeyGenerator {
    output_path: PathBuf,
    key_type: SshKeyType,
    bits: Option<usize>,
    passphrase: Option<String>,
    comment: Option<String>,
}

impl SshKeyGenerator {
    /// Creates a new SSH key generator with the specified output path.
    ///
    /// # Arguments
    ///
    /// * `output_path` - Path where the private key will be saved (public key will be saved with .pub extension)
    pub fn new<P: AsRef<Path>>(output_path: P) -> Self {
        Self {
            output_path: output_path.as_ref().to_path_buf(),
            key_type: SshKeyType::Ed25519,
            bits: None,
            passphrase: None,
            comment: None,
        }
    }

    /// Sets the SSH key type (default: ED25519).
    pub fn key_type(mut self, kt: SshKeyType) -> Self {
        self.key_type = kt;
        self
    }

    /// Sets the number of bits for RSA keys (default: 4096).
    /// This is ignored for ED25519 keys.
    pub fn bits(mut self, bits: usize) -> Self {
        self.bits = Some(bits);
        self
    }

    /// Sets the passphrase for the private key.
    /// If not set, the key will be generated without a passphrase.
    pub fn passphrase<S: Into<String>>(mut self, passphrase: S) -> Self {
        self.passphrase = Some(passphrase.into());
        self
    }

    /// Sets a comment for the key (typically user@hostname).
    pub fn comment<S: Into<String>>(mut self, comment: S) -> Self {
        self.comment = Some(comment.into());
        self
    }

    /// Generates the SSH key pair.
    ///
    /// # Returns
    ///
    /// Returns the path to the generated private key on success.
    ///
    /// # Errors
    ///
    /// Returns an error if the key generation fails or if ssh-keygen is not available.
    pub fn generate(self) -> Result<PathBuf> {
        // Build the ssh-keygen command
        let mut cmd = Command::new("ssh-keygen");

        // Add key type
        cmd.arg("-t").arg(self.key_type.as_str());

        // Add bits if specified (for RSA keys)
        if let Some(bits) = self.bits {
            cmd.arg("-b").arg(bits.to_string());
        }

        // Add output path
        cmd.arg("-f").arg(&self.output_path);

        // Add passphrase
        match self.passphrase {
            Some(ref pass) => {
                cmd.arg("-N").arg(pass);
            }
            None => {
                // Empty passphrase
                cmd.arg("-N").arg("");
            }
        }

        // Add comment if provided
        if let Some(ref comment) = self.comment {
            cmd.arg("-C").arg(comment);
        }

        // Disable confirmation prompts
        cmd.arg("-q");

        // Execute the command
        let output = cmd.output()?;

        if !output.status.success() {
            let error_msg = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("SSH key generation failed: {}", error_msg);
        }

        Ok(self.output_path)
    }

    /// Returns the output path for the private key.
    pub fn output_path(&self) -> &Path {
        &self.output_path
    }

    /// Returns the path for the public key (output_path + ".pub").
    pub fn public_key_path(&self) -> PathBuf {
        let mut path = self.output_path.clone();
        let file_name = format!("{}.pub", path.file_name().unwrap_or_default().to_string_lossy());
        path.set_file_name(file_name);
        path
    }

    /// Returns the key type.
    pub fn get_key_type(&self) -> SshKeyType {
        self.key_type
    }

    /// Checks if the key has a passphrase.
    pub fn has_passphrase(&self) -> bool {
        self.passphrase.is_some()
    }
}

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

    #[test]
    fn test_key_type_as_str() {
        assert_eq!(SshKeyType::Rsa.as_str(), "rsa");
        assert_eq!(SshKeyType::Ed25519.as_str(), "ed25519");
    }

    #[test]
    fn test_key_builder() {
        let key_gen = SshKeyGenerator::new("/tmp/test_key")
            .key_type(SshKeyType::Rsa)
            .bits(2048)
            .passphrase("test")
            .comment("test@local");

        assert_eq!(key_gen.get_key_type(), SshKeyType::Rsa);
        assert!(key_gen.has_passphrase());
        assert_eq!(key_gen.public_key_path(), PathBuf::from("/tmp/test_key.pub"));
    }
}
