use anyhow::Result;
use std::path::Path;

use super::ssh::SshConnection;

/// Manages authorized keys on a remote SSH server.
///
/// Provides functionality to:
/// - Add public keys to remote authorized_keys
/// - Remove public keys from remote authorized_keys
/// - List all authorized keys on the remote system
pub struct AuthorizedKeysManager {
    connection: SshConnection,
}

impl AuthorizedKeysManager {
    /// Creates a new authorized keys manager for a given SSH connection.
    pub fn new(connection: SshConnection) -> Self {
        Self { connection }
    }

    /// Adds a public key to the remote authorized_keys file.
    ///
    /// # Arguments
    ///
    /// * `public_key_path` - Local path to the public key file (.pub)
    ///
    /// # Returns
    ///
    /// Returns the exit code and output from the operation.
    ///
    /// # Errors
    ///
    /// Returns an error if the SSH connection fails or the key file cannot be read.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_os::ssh::ssh::SshConnection;
    /// use herolib_os::ssh::auth_keys::AuthorizedKeysManager;
    ///
    /// let conn = SshConnection::builder()
    ///     .host("example.com")
    ///     .user("ubuntu")
    ///     .build();
    ///
    /// let manager = AuthorizedKeysManager::new(conn);
    /// let (code, output) = manager.add_key("/path/to/id_rsa.pub")?;
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn add_key<P: AsRef<Path>>(&self, public_key_path: P) -> Result<(i32, String)> {
        let key_content = std::fs::read_to_string(&public_key_path)?;
        let key_content = key_content.trim();

        // Create the ~/.ssh directory if it doesn't exist
        self.connection.execute("mkdir -p ~/.ssh && chmod 700 ~/.ssh")?;

        // Append the key to authorized_keys
        let cmd = format!(
            "echo '{}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys",
            key_content
        );

        self.connection.execute(&cmd)
    }

    /// Removes a specific public key from the remote authorized_keys file.
    ///
    /// # Arguments
    ///
    /// * `public_key_path` - Local path to the public key file to remove
    ///
    /// # Returns
    ///
    /// Returns the exit code and output from the operation.
    ///
    /// # Errors
    ///
    /// Returns an error if the SSH connection fails or the key file cannot be read.
    pub fn remove_key<P: AsRef<Path>>(&self, public_key_path: P) -> Result<(i32, String)> {
        let key_content = std::fs::read_to_string(&public_key_path)?;
        let key_content = key_content.trim();

        // Escape single quotes in the key for safe use in grep
        let escaped_key = key_content.replace("'", "'\\''");

        // Remove the key using grep -v
        let cmd = format!(
            "grep -v '{}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp && \
             mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys && \
             chmod 600 ~/.ssh/authorized_keys",
            escaped_key
        );

        self.connection.execute(&cmd)
    }

    /// Lists all authorized keys on the remote system.
    ///
    /// # Returns
    ///
    /// Returns a vector of public key strings, one per line.
    ///
    /// # Errors
    ///
    /// Returns an error if the SSH connection fails or authorized_keys doesn't exist.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use herolib_os::ssh::ssh::SshConnection;
    /// use herolib_os::ssh::auth_keys::AuthorizedKeysManager;
    ///
    /// let conn = SshConnection::builder()
    ///     .host("example.com")
    ///     .user("ubuntu")
    ///     .build();
    ///
    /// let manager = AuthorizedKeysManager::new(conn);
    /// let keys = manager.list_keys()?;
    /// for key in keys {
    ///     println!("Key: {}", key);
    /// }
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn list_keys(&self) -> Result<Vec<String>> {
        // Check if authorized_keys exists first
        let (code, output) = self.connection.execute("test -f ~/.ssh/authorized_keys && cat ~/.ssh/authorized_keys || echo ''")?;

        if code != 0 && !output.is_empty() {
            // If the file doesn't exist or there's an actual error
            if code == 1 && output.is_empty() {
                // File doesn't exist - return empty list
                return Ok(Vec::new());
            }
            anyhow::bail!("Failed to list authorized keys: {}", output);
        }

        // Parse the output into individual keys
        let keys = output
            .lines()
            .filter(|line| !line.trim().is_empty() && !line.starts_with('#'))
            .map(|line| line.to_string())
            .collect();

        Ok(keys)
    }

    /// Checks if a specific public key is in the remote authorized_keys.
    ///
    /// # Arguments
    ///
    /// * `public_key_path` - Local path to the public key file
    ///
    /// # Returns
    ///
    /// Returns `true` if the key is found in authorized_keys.
    ///
    /// # Errors
    ///
    /// Returns an error if the SSH connection fails or the key file cannot be read.
    pub fn key_exists<P: AsRef<Path>>(&self, public_key_path: P) -> Result<bool> {
        let keys = self.list_keys()?;
        let key_content = std::fs::read_to_string(&public_key_path)?;
        let key_content = key_content.trim();

        Ok(keys.iter().any(|k| k.trim() == key_content))
    }

    /// Returns a reference to the underlying SSH connection.
    pub fn connection(&self) -> &SshConnection {
        &self.connection
    }
}

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

    #[test]
    fn test_manager_creation() {
        let conn = SshConnection::builder()
            .host("localhost")
            .user("root")
            .build();
        let manager = AuthorizedKeysManager::new(conn);
        assert_eq!(manager.connection().host(), "localhost");
    }
}
