//! SSH Key Deployer
//!
//! Deploys SSH public keys to servers and manages authorized_keys file.

use anyhow::Result;
use chrono::Utc;
use platform_clients::MachinesClient;
use tracing::{debug, error, info, warn};

use super::{SshKeyDeployment, TemporalSshKey};

/// SSH key deployer using HTTP machines service
pub struct KeyDeployer {
    /// HTTP client for machines service
    machines_client: MachinesClient,
}

impl KeyDeployer {
    /// Create new key deployer with machines service URL
    pub fn new(machines_service_url: impl Into<String>) -> Result<Self> {
        let machines_client = MachinesClient::new(machines_service_url)?;
        Ok(Self { machines_client })
    }

    /// Deploy SSH key to target server
    pub async fn deploy(&self, key: &TemporalSshKey) -> Result<SshKeyDeployment> {
        info!(
            "Deploying SSH key {} to {}@{}",
            key.id, key.user, key.target_server
        );

        let result = self.deploy_key_internal(key).await;

        match &result {
            Ok(_) => {
                info!(
                    "Successfully deployed key {} to {}",
                    key.id, key.target_server
                );
                Ok(SshKeyDeployment {
                    key_id: key.id.clone(),
                    server: key.target_server.clone(),
                    success: true,
                    error: None,
                    deployed_at: Utc::now(),
                })
            }
            Err(e) => {
                warn!(
                    "Failed to deploy key {} to {}: {}",
                    key.id, key.target_server, e
                );
                Ok(SshKeyDeployment {
                    key_id: key.id.clone(),
                    server: key.target_server.clone(),
                    success: false,
                    error: Some(e.to_string()),
                    deployed_at: Utc::now(),
                })
            }
        }
    }

    /// Internal deploy implementation
    async fn deploy_key_internal(&self, key: &TemporalSshKey) -> Result<()> {
        // Build authorized_keys entry with expiration comment
        let entry = self.build_authorized_keys_entry(key)?;

        // Build shell script to deploy key via machines service
        let script = format!(
            "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '{}' | tee -a ~/.ssh/authorized_keys > \
             /dev/null && chmod 600 ~/.ssh/authorized_keys",
            entry
        );

        debug!(
            "Deploying key via machines service to {}@{}",
            key.user, key.target_server
        );

        // Call machines service to execute SSH command
        let response = self
            .machines_client
            .execute_command(&key.target_server, script)
            .await?;

        if response.exit_code != 0 {
            anyhow::bail!(
                "SSH command failed with exit code {}: {}",
                response.exit_code,
                response.error.unwrap_or_default()
            );
        }

        Ok(())
    }

    /// Remove SSH key from server
    pub async fn remove(&self, key: &TemporalSshKey) -> Result<()> {
        info!(
            "Removing SSH key {} from {}@{}",
            key.id, key.user, key.target_server
        );

        let result = self.remove_key_internal(key).await;

        match result {
            Ok(_) => {
                info!(
                    "Successfully removed key {} from {}",
                    key.id, key.target_server
                );
                Ok(())
            }
            Err(e) => {
                error!(
                    "Failed to remove key {} from {}: {}",
                    key.id, key.target_server, e
                );
                Err(e)
            }
        }
    }

    /// Internal remove implementation
    async fn remove_key_internal(&self, key: &TemporalSshKey) -> Result<()> {
        // Use sed to remove the key line (matching by key ID in comment)
        let script = format!(
            "sed -i.bak '/# KeyID: {}/d' ~/.ssh/authorized_keys || true",
            key.id
        );

        debug!(
            "Removing key via machines service from {}@{}",
            key.user, key.target_server
        );

        // Call machines service to execute SSH command
        let response = self
            .machines_client
            .execute_command(&key.target_server, script)
            .await?;

        if response.exit_code != 0 {
            warn!(
                "SSH command returned exit code {}: {}",
                response.exit_code,
                response.error.unwrap_or_default()
            );
            // Don't fail on removal errors - key might already be gone
        }

        Ok(())
    }

    /// Build authorized_keys entry
    fn build_authorized_keys_entry(&self, key: &TemporalSshKey) -> Result<String> {
        // Format: public_key # KeyID: id, Expires: timestamp
        let expires = key.expires_at.to_rfc3339();
        Ok(format!(
            "{} # KeyID: {}, Expires: {}",
            key.public_key, key.id, expires
        ))
    }

    /// Test SSH connection to server via machines service
    pub async fn test_connection(&self, server: &str) -> Result<()> {
        debug!("Testing SSH connection to {} via machines service", server);

        let response = self
            .machines_client
            .execute_command(server, "echo 'SSH connection test'")
            .await?;

        if response.exit_code != 0 {
            anyhow::bail!(
                "SSH connection test failed: {}",
                response.error.unwrap_or_default()
            );
        }

        Ok(())
    }
}

impl KeyDeployer {
    /// Create key deployer with default machines service URL
    ///
    /// Uses `http://localhost:8081` as default machines service endpoint.
    /// Override by setting machines service URL in configuration.
    pub fn with_default_service() -> Result<Self> {
        Self::new("http://localhost:8081")
    }
}

#[cfg(test)]
mod tests {
    use chrono::Duration;

    use super::*;
    use crate::ssh::SshKeyType;

    fn create_test_key() -> TemporalSshKey {
        let now = Utc::now();
        TemporalSshKey {
            id: "test-key-123".to_string(),
            key_type: SshKeyType::DynamicKeyPair,
            public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest test@example.com".to_string(),
            private_key: None,
            fingerprint: "SHA256:test123".to_string(),
            user: "root".to_string(),
            target_server: "test.example.com".to_string(),
            created_at: now,
            expires_at: now + Duration::hours(1),
            ttl: Duration::hours(1),
            used: false,
            deployed: false,
            vault_lease_id: None,
        }
    }

    #[test]
    fn test_key_deployer_creation() {
        let result = KeyDeployer::new("http://localhost:8081");
        assert!(result.is_ok());
    }

    #[test]
    fn test_key_deployer_invalid_url() {
        let result = KeyDeployer::new("not-a-valid-url");
        assert!(result.is_err());
    }

    #[test]
    fn test_build_authorized_keys_entry() {
        let deployer = KeyDeployer::new("http://localhost:8081").unwrap();
        let key = create_test_key();

        let entry = deployer.build_authorized_keys_entry(&key).unwrap();

        assert!(entry.contains("ssh-ed25519"));
        assert!(entry.contains("# KeyID: test-key-123"));
        assert!(entry.contains("Expires:"));
    }

    #[test]
    fn test_with_default_service() {
        let result = KeyDeployer::with_default_service();
        assert!(result.is_ok());
    }
}
