//! SSH Key Management Module
//!
//! Provides temporal SSH key generation, deployment, and automatic cleanup.
//! Supports multiple key types: OTP (Vault), CA-signed certificates (Vault),
//! and dynamic key pairs.

use std::collections::HashMap;
use std::sync::Arc;

use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

pub mod api;
pub mod authorized_keys;
pub mod key_deployer;
pub mod key_generator;
pub mod pool;
pub mod temporal_manager;
pub mod vault_ssh_engine; // SSH connection pooling (Phase 1+)

#[cfg(test)]
mod tests;

pub use api::{create_ssh_routes, SshManagerState};
pub use authorized_keys::AuthorizedKeysManager;
pub use key_deployer::KeyDeployer;
pub use key_generator::KeyGenerator;
pub use temporal_manager::TemporalManager;
pub use vault_ssh_engine::VaultSshEngine;

/// SSH key types supported by the system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SshKeyType {
    /// One-time password generated by Vault
    Otp,
    /// Certificate signed by Vault CA
    Certificate,
    /// Dynamically generated key pair
    DynamicKeyPair,
}

/// Temporal SSH key with automatic expiration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalSshKey {
    /// Unique identifier
    pub id: String,
    /// Key type (OTP, Certificate, or DynamicKeyPair)
    pub key_type: SshKeyType,
    /// Public key (OpenSSH format)
    pub public_key: String,
    /// Private key (only for dynamic keys, PEM format)
    pub private_key: Option<String>,
    /// SSH key fingerprint (SHA256)
    pub fingerprint: String,
    /// Target user (e.g., "root", "deploy")
    pub user: String,
    /// Target server (e.g., "server01.example.com")
    pub target_server: String,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
    /// Expiration timestamp
    pub expires_at: DateTime<Utc>,
    /// Time-to-live duration
    #[serde(with = "chrono_duration_serde")]
    pub ttl: Duration,
    /// Whether key has been used
    pub used: bool,
    /// Whether key has been deployed
    pub deployed: bool,
    /// Vault lease ID (for OTP and Certificate types)
    pub vault_lease_id: Option<String>,
}

/// SSH key generation request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshKeyRequest {
    /// Key type to generate
    pub key_type: SshKeyType,
    /// Target user
    pub user: String,
    /// Target server
    pub target_server: String,
    /// Time-to-live (default: 1 hour)
    #[serde(default = "default_ttl", with = "chrono_duration_serde")]
    pub ttl: Duration,
    /// IP address to allow (optional, for Vault OTP)
    pub allowed_ip: Option<String>,
    /// Principal (for Vault certificates)
    pub principal: Option<String>,
}

/// SSH key deployment result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshKeyDeployment {
    /// Key ID
    pub key_id: String,
    /// Target server
    pub server: String,
    /// Deployment successful
    pub success: bool,
    /// Error message if deployment failed
    pub error: Option<String>,
    /// Deployment timestamp
    pub deployed_at: DateTime<Utc>,
}

/// SSH key statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshKeyStats {
    /// Total keys generated
    pub total_generated: usize,
    /// Active keys (not expired)
    pub active_keys: usize,
    /// Expired keys
    pub expired_keys: usize,
    /// Keys by type
    pub keys_by_type: HashMap<String, usize>,
    /// Keys cleaned up in last run
    pub last_cleanup_count: usize,
    /// Last cleanup timestamp
    pub last_cleanup_at: Option<DateTime<Utc>>,
}

/// Main SSH key manager
pub struct SshKeyManager {
    /// Optional Vault client
    vault_client: Option<Arc<VaultSshEngine>>,
    /// Key storage
    key_storage: Arc<RwLock<HashMap<String, TemporalSshKey>>>,
    /// Key generator
    key_generator: Arc<KeyGenerator>,
    /// Key deployer
    key_deployer: Arc<KeyDeployer>,
    /// Temporal manager
    temporal_manager: Arc<TemporalManager>,
    /// Configuration
    config: SshConfig,
}

/// SSH configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshConfig {
    /// Enable Vault integration
    #[serde(default)]
    pub vault_enabled: bool,
    /// Vault address
    pub vault_addr: Option<String>,
    /// Vault token
    pub vault_token: Option<String>,
    /// Vault SSH mount point
    #[serde(default = "default_vault_mount")]
    pub vault_mount_point: String,
    /// Vault SSH mode (ca or otp)
    #[serde(default = "default_vault_mode")]
    pub vault_mode: String,
    /// Default TTL
    #[serde(default = "default_ttl", with = "chrono_duration_serde")]
    pub default_ttl: Duration,
    /// Cleanup interval
    #[serde(default = "default_cleanup_interval", with = "chrono_duration_serde")]
    pub cleanup_interval: Duration,
    /// Machines service URL (for SSH key deployment via HTTP API)
    #[serde(default = "default_machines_service_url")]
    pub machines_service_url: String,
}

impl Default for SshConfig {
    fn default() -> Self {
        Self {
            vault_enabled: false,
            vault_addr: None,
            vault_token: None,
            vault_mount_point: default_vault_mount(),
            vault_mode: default_vault_mode(),
            default_ttl: default_ttl(),
            cleanup_interval: default_cleanup_interval(),
            machines_service_url: default_machines_service_url(),
        }
    }
}

impl SshKeyManager {
    /// Create new SSH key manager
    pub async fn new(config: SshConfig) -> Result<Self> {
        let vault_client = if config.vault_enabled {
            Some(Arc::new(
                VaultSshEngine::new(
                    config
                        .vault_addr
                        .as_ref()
                        .context("Vault address required")?,
                    config
                        .vault_token
                        .as_ref()
                        .context("Vault token required")?,
                    &config.vault_mount_point,
                    &config.vault_mode,
                )
                .await?,
            ))
        } else {
            None
        };

        let key_storage = Arc::new(RwLock::new(HashMap::new()));
        let key_generator = Arc::new(KeyGenerator::new());
        let key_deployer = Arc::new(
            KeyDeployer::new(&config.machines_service_url)
                .context("Failed to create key deployer with machines service URL")?,
        );
        let temporal_manager = Arc::new(TemporalManager::new(
            Arc::clone(&key_storage),
            Arc::clone(&key_deployer),
            config.cleanup_interval,
        ));

        Ok(Self {
            vault_client,
            key_storage,
            key_generator,
            key_deployer,
            temporal_manager,
            config,
        })
    }

    /// Generate SSH key
    pub async fn generate_key(&self, request: SshKeyRequest) -> Result<TemporalSshKey> {
        let key = match request.key_type {
            SshKeyType::Otp => {
                let vault = self
                    .vault_client
                    .as_ref()
                    .context("Vault not configured for OTP")?;
                vault.generate_otp(&request).await?
            }
            SshKeyType::Certificate => {
                let vault = self
                    .vault_client
                    .as_ref()
                    .context("Vault not configured for certificates")?;
                vault.sign_certificate(&request).await?
            }
            SshKeyType::DynamicKeyPair => self.key_generator.generate_keypair(&request).await?,
        };

        // Store key
        let mut storage = self.key_storage.write().await;
        storage.insert(key.id.clone(), key.clone());

        Ok(key)
    }

    /// Deploy SSH key to target server
    pub async fn deploy_key(&self, key_id: &str) -> Result<SshKeyDeployment> {
        let key = {
            let storage = self.key_storage.read().await;
            storage.get(key_id).cloned().context("Key not found")?
        };

        let result = self.key_deployer.deploy(&key).await?;

        // Mark as deployed
        let mut storage = self.key_storage.write().await;
        if let Some(k) = storage.get_mut(key_id) {
            k.deployed = result.success;
        }

        Ok(result)
    }

    /// Revoke SSH key immediately
    pub async fn revoke_key(&self, key_id: &str) -> Result<()> {
        let key = {
            let mut storage = self.key_storage.write().await;
            storage.remove(key_id).context("Key not found")?
        };

        // Remove from server if deployed
        if key.deployed {
            self.key_deployer.remove(&key).await?;
        }

        // Revoke in Vault if applicable
        if let Some(lease_id) = &key.vault_lease_id {
            if let Some(vault) = &self.vault_client {
                vault.revoke_lease(lease_id).await?;
            }
        }

        Ok(())
    }

    /// List active SSH keys
    pub async fn list_keys(&self, include_expired: bool) -> Result<Vec<TemporalSshKey>> {
        let storage = self.key_storage.read().await;
        let now = Utc::now();

        let keys: Vec<TemporalSshKey> = storage
            .values()
            .filter(|k| include_expired || k.expires_at > now)
            .cloned()
            .collect();

        Ok(keys)
    }

    /// Get SSH key by ID
    pub async fn get_key(&self, key_id: &str) -> Result<TemporalSshKey> {
        let storage = self.key_storage.read().await;
        storage.get(key_id).cloned().context("Key not found")
    }

    /// Get SSH key statistics
    pub async fn get_stats(&self) -> Result<SshKeyStats> {
        let storage = self.key_storage.read().await;
        let now = Utc::now();

        let total_generated = storage.len();
        let active_keys = storage.values().filter(|k| k.expires_at > now).count();
        let expired_keys = total_generated - active_keys;

        let mut keys_by_type = HashMap::new();
        for key in storage.values() {
            let type_name = match key.key_type {
                SshKeyType::Otp => "otp",
                SshKeyType::Certificate => "certificate",
                SshKeyType::DynamicKeyPair => "dynamic",
            };
            *keys_by_type.entry(type_name.to_string()).or_insert(0) += 1;
        }

        let (last_cleanup_count, last_cleanup_at) =
            self.temporal_manager.get_last_cleanup_stats().await;

        Ok(SshKeyStats {
            total_generated,
            active_keys,
            expired_keys,
            keys_by_type,
            last_cleanup_count,
            last_cleanup_at,
        })
    }

    /// Trigger manual cleanup of expired keys
    pub async fn cleanup_expired(&self) -> Result<Vec<String>> {
        self.temporal_manager.cleanup_expired_keys().await
    }

    /// Start background cleanup task
    pub async fn start_cleanup_task(self: Arc<Self>) {
        let manager = Arc::clone(&self.temporal_manager);
        tokio::spawn(async move {
            manager.start_cleanup_task().await;
        });
    }
}

// Helper functions for defaults
fn default_ttl() -> Duration {
    Duration::hours(1)
}

fn default_cleanup_interval() -> Duration {
    Duration::minutes(5)
}

fn default_vault_mount() -> String {
    "ssh".to_string()
}

fn default_vault_mode() -> String {
    "ca".to_string()
}

fn default_machines_service_url() -> String {
    "http://localhost:8081".to_string()
}

// Custom serde for chrono::Duration
mod chrono_duration_serde {
    use chrono::Duration;
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_i64(duration.num_seconds())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
    where
        D: Deserializer<'de>,
    {
        let seconds = i64::deserialize(deserializer)?;
        Ok(Duration::seconds(seconds))
    }
}
