//! SSH key operations.
//!
//! This module provides functions for reading and validating SSH keys.
//!
//! # Example
//!
//! ```no_run
//! use herolib_os::ssh::{ssh_key_read, ssh_key_validate};
//!
//! let key_content = ssh_key_read("/home/user/.ssh/id_rsa.pub")?;
//! if ssh_key_validate(&key_content)? {
//!     println!("Key is valid");
//! }
//! # Ok::<(), herolib_os::ssh::SshError>(())
//! ```

use herolib_core::text::path_fix;
use std::path::Path;

/// Error type for SSH operations
#[derive(Debug)]
pub enum SshError {
    /// IO error
    IoError(std::io::Error),
    /// File not found
    FileNotFound(String),
    /// Invalid key format
    InvalidKeyFormat(String),
    /// Permission denied
    PermissionDenied(String),
    /// Other error
    Other(String),
}

impl std::fmt::Display for SshError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SshError::IoError(e) => write!(f, "IO error: {}", e),
            SshError::FileNotFound(path) => write!(f, "File not found: {}", path),
            SshError::InvalidKeyFormat(msg) => write!(f, "Invalid key format: {}", msg),
            SshError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
            SshError::Other(msg) => write!(f, "Error: {}", msg),
        }
    }
}

impl std::error::Error for SshError {}

impl From<std::io::Error> for SshError {
    fn from(e: std::io::Error) -> Self {
        SshError::IoError(e)
    }
}

/// Read an SSH key from a file
///
/// Reads and returns the contents of an SSH key file.
/// The file can be a public key (.pub) or private key.
///
/// # Arguments
///
/// * `path` - Path to the SSH key file (e.g., "/home/user/.ssh/id_rsa.pub")
///
/// # Returns
///
/// * `Ok(String)` - The key content
/// * `Err(SshError)` - If reading failed
///
/// # Example
///
/// ```no_run
/// use herolib_os::ssh::ssh_key_read;
///
/// let key = ssh_key_read("/home/user/.ssh/id_rsa.pub")?;
/// println!("Key: {}", key);
/// # Ok::<(), herolib_os::ssh::SshError>(())
/// ```
pub fn ssh_key_read(path: &str) -> Result<String, SshError> {
    let path = path_fix(path);
    if !Path::new(&path).exists() {
        return Err(SshError::FileNotFound(path.to_string()));
    }

    std::fs::read_to_string(&path).map_err(SshError::IoError)
}

/// Validate an SSH key's format
///
/// Checks if the provided SSH key has a valid format by looking for valid key type prefixes
/// and basic structure. Supports various SSH key types:
/// - ssh-rsa
/// - ssh-dss
/// - ssh-ed25519
/// - ecdsa-sha2-nistp256
/// - ecdsa-sha2-nistp384
/// - ecdsa-sha2-nistp521
/// - ssh-rsa-cert-v01@openssh.com
/// - ssh-ed25519-cert-v01@openssh.com
///
/// # Arguments
///
/// * `key_content` - SSH key content to validate
///
/// # Returns
///
/// * `Ok(true)` - Key format is valid
/// * `Ok(false)` - Key format is not valid
/// * `Err(SshError)` - If validation could not be performed
///
/// # Example
///
/// ```no_run
/// use herolib_os::ssh::{ssh_key_read, ssh_key_validate};
///
/// let key = ssh_key_read("/home/user/.ssh/id_rsa.pub")?;
/// if ssh_key_validate(&key)? {
///     println!("SSH key is valid");
/// } else {
///     println!("SSH key is invalid");
/// }
/// # Ok::<(), herolib_os::ssh::SshError>(())
/// ```
pub fn ssh_key_validate(key_content: &str) -> Result<bool, SshError> {
    if key_content.trim().is_empty() {
        return Ok(false);
    }

    // Valid SSH key type prefixes
    let valid_types = [
        "ssh-rsa",
        "ssh-dss",
        "ssh-ed25519",
        "ecdsa-sha2-nistp256",
        "ecdsa-sha2-nistp384",
        "ecdsa-sha2-nistp521",
        "ssh-rsa-cert-v01@openssh.com",
        "ssh-ed25519-cert-v01@openssh.com",
        "ecdsa-sha2-nistp256-cert-v01@openssh.com",
        "ecdsa-sha2-nistp384-cert-v01@openssh.com",
        "ecdsa-sha2-nistp521-cert-v01@openssh.com",
    ];

    // For public keys, check if it starts with a valid key type
    for key_type in &valid_types {
        if key_content.trim().starts_with(key_type) {
            // Additional validation: public keys should have space-separated components
            let parts: Vec<&str> = key_content.trim().split_whitespace().collect();
            // At minimum: type and key data (comment is optional)
            if parts.len() >= 2 {
                return Ok(true);
            }
        }
    }

    // For private keys, check for OpenSSH format
    if key_content.contains("-----BEGIN") && key_content.contains("-----END") {
        if key_content.contains("PRIVATE KEY") {
            return Ok(true);
        }
    }

    // Could not identify as valid key
    Ok(false)
}

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

    #[test]
    fn test_ssh_key_validate_public_key() {
        // Valid SSH public key format
        let key = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2j3wZrD user@host";
        let result = ssh_key_validate(key);
        assert!(result.is_ok());
        assert!(result.unwrap());
    }

    #[test]
    fn test_ssh_key_validate_ed25519() {
        // Valid ED25519 public key format
        let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGfMZlIFRE user@host";
        let result = ssh_key_validate(key);
        assert!(result.is_ok());
        assert!(result.unwrap());
    }

    #[test]
    fn test_ssh_key_validate_invalid() {
        let key = "invalid key format";
        let result = ssh_key_validate(key);
        assert!(result.is_ok());
        assert!(!result.unwrap());
    }

    #[test]
    fn test_ssh_key_validate_empty() {
        let result = ssh_key_validate("");
        assert!(result.is_ok());
        assert!(!result.unwrap());
    }
}
