//! Comprehensive tests for SSH key management system

use chrono::Duration;

use crate::ssh::*;

// Helper function to create test SSH config
fn create_test_config() -> SshConfig {
    SshConfig {
        vault_enabled: false,
        vault_addr: None,
        vault_token: None,
        vault_mount_point: "ssh".to_string(),
        vault_mode: "ca".to_string(),
        default_ttl: Duration::hours(1),
        cleanup_interval: Duration::minutes(5),
        machines_service_url: "http://localhost:8081".to_string(),
    }
}

#[tokio::test]
async fn test_ssh_key_manager_initialization() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await;
    assert!(manager.is_ok());
}

#[tokio::test]
async fn test_generate_dynamic_keypair() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "testuser".to_string(),
        target_server: "test.example.com".to_string(),
        ttl: Duration::hours(1),
        allowed_ip: None,
        principal: None,
    };

    let result = manager.generate_key(request).await;
    assert!(result.is_ok());

    let key = result.unwrap();
    assert_eq!(key.key_type, SshKeyType::DynamicKeyPair);
    assert!(key.public_key.starts_with("ssh-ed25519"));
    assert!(key.private_key.is_some());
    assert!(key.fingerprint.starts_with("SHA256:"));
    assert_eq!(key.user, "testuser");
    assert_eq!(key.target_server, "test.example.com");
    assert!(!key.deployed);
}

#[tokio::test]
async fn test_list_keys() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    // Generate a few keys
    for i in 0..3 {
        let request = SshKeyRequest {
            key_type: SshKeyType::DynamicKeyPair,
            user: format!("user{}", i),
            target_server: format!("server{}.example.com", i),
            ttl: Duration::hours(1),
            allowed_ip: None,
            principal: None,
        };
        manager.generate_key(request).await.unwrap();
    }

    let keys = manager.list_keys(false).await.unwrap();
    assert_eq!(keys.len(), 3);
}

#[tokio::test]
async fn test_get_key_by_id() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "testuser".to_string(),
        target_server: "test.example.com".to_string(),
        ttl: Duration::hours(1),
        allowed_ip: None,
        principal: None,
    };

    let generated_key = manager.generate_key(request).await.unwrap();
    let retrieved_key = manager.get_key(&generated_key.id).await.unwrap();

    assert_eq!(generated_key.id, retrieved_key.id);
    assert_eq!(generated_key.user, retrieved_key.user);
}

#[tokio::test]
async fn test_revoke_key() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "testuser".to_string(),
        target_server: "test.example.com".to_string(),
        ttl: Duration::hours(1),
        allowed_ip: None,
        principal: None,
    };

    let key = manager.generate_key(request).await.unwrap();
    let key_id = key.id.clone();

    // Revoke the key
    let result = manager.revoke_key(&key_id).await;
    assert!(result.is_ok());

    // Try to get the key - should fail
    let result = manager.get_key(&key_id).await;
    assert!(result.is_err());
}

#[tokio::test]
async fn test_ssh_key_stats() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    // Generate some keys
    for i in 0..5 {
        let request = SshKeyRequest {
            key_type: SshKeyType::DynamicKeyPair,
            user: format!("user{}", i),
            target_server: format!("server{}.example.com", i),
            ttl: Duration::hours(1),
            allowed_ip: None,
            principal: None,
        };
        manager.generate_key(request).await.unwrap();
    }

    let stats = manager.get_stats().await.unwrap();
    assert_eq!(stats.total_generated, 5);
    assert_eq!(stats.active_keys, 5);
    assert_eq!(stats.expired_keys, 0);
}

#[tokio::test]
async fn test_cleanup_expired_keys() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    // Generate an expired key (negative TTL for testing)
    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "testuser".to_string(),
        target_server: "test.example.com".to_string(),
        ttl: Duration::seconds(-60), // Already expired
        allowed_ip: None,
        principal: None,
    };

    manager.generate_key(request).await.unwrap();

    // Run cleanup
    let cleaned = manager.cleanup_expired().await.unwrap();
    assert_eq!(cleaned.len(), 1);
}

#[test]
fn test_ssh_key_type_serialization() {
    let key_type = SshKeyType::DynamicKeyPair;
    let json = serde_json::to_string(&key_type).unwrap();
    assert_eq!(json, "\"dynamickeypair\"");

    let key_type = SshKeyType::Otp;
    let json = serde_json::to_string(&key_type).unwrap();
    assert_eq!(json, "\"otp\"");

    let key_type = SshKeyType::Certificate;
    let json = serde_json::to_string(&key_type).unwrap();
    assert_eq!(json, "\"certificate\"");
}

#[test]
fn test_ssh_config_defaults() {
    let config = SshConfig::default();
    assert!(!config.vault_enabled);
    assert_eq!(config.vault_mount_point, "ssh");
    assert_eq!(config.vault_mode, "ca");
    assert_eq!(config.default_ttl, Duration::hours(1));
    assert_eq!(config.cleanup_interval, Duration::minutes(5));
}

#[tokio::test]
async fn test_key_expiration_logic() {
    let config = create_test_config();
    let manager = SshKeyManager::new(config).await.unwrap();

    // Generate key with very short TTL
    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "testuser".to_string(),
        target_server: "test.example.com".to_string(),
        ttl: Duration::seconds(1),
        allowed_ip: None,
        principal: None,
    };

    let key = manager.generate_key(request).await.unwrap();
    assert!(!key.used);

    // Wait for expiration
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    // Check if key is expired
    let stats = manager.get_stats().await.unwrap();
    assert!(stats.expired_keys > 0);
}

#[test]
fn test_ssh_key_request_validation() {
    let request = SshKeyRequest {
        key_type: SshKeyType::DynamicKeyPair,
        user: "root".to_string(),
        target_server: "server.example.com".to_string(),
        ttl: Duration::hours(1),
        allowed_ip: Some("192.168.1.100".to_string()),
        principal: Some("admin".to_string()),
    };

    assert_eq!(request.user, "root");
    assert_eq!(request.ttl.num_hours(), 1);
    assert!(request.allowed_ip.is_some());
    assert!(request.principal.is_some());
}

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

    use super::super::*;

    #[tokio::test]
    async fn test_full_key_lifecycle() {
        let config = SshConfig::default();
        let manager = SshKeyManager::new(config).await.unwrap();

        // 1. Generate key
        let request = SshKeyRequest {
            key_type: SshKeyType::DynamicKeyPair,
            user: "deploy".to_string(),
            target_server: "prod.example.com".to_string(),
            ttl: Duration::hours(2),
            allowed_ip: None,
            principal: None,
        };

        let key = manager.generate_key(request).await.unwrap();
        let key_id = key.id.clone();

        // 2. Verify key exists
        let retrieved = manager.get_key(&key_id).await.unwrap();
        assert_eq!(retrieved.id, key_id);

        // 3. Check stats
        let stats = manager.get_stats().await.unwrap();
        assert!(stats.total_generated > 0);

        // 4. List keys
        let keys = manager.list_keys(false).await.unwrap();
        assert!(!keys.is_empty());

        // 5. Revoke key
        manager.revoke_key(&key_id).await.unwrap();

        // 6. Verify key is gone
        let result = manager.get_key(&key_id).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_concurrent_key_generation() {
        let config = SshConfig::default();
        let manager = std::sync::Arc::new(SshKeyManager::new(config).await.unwrap());

        let mut handles = vec![];

        // Generate 10 keys concurrently
        for i in 0..10 {
            let manager_clone = manager.clone();
            let handle = tokio::spawn(async move {
                let request = SshKeyRequest {
                    key_type: SshKeyType::DynamicKeyPair,
                    user: format!("user{}", i),
                    target_server: format!("server{}.example.com", i),
                    ttl: Duration::hours(1),
                    allowed_ip: None,
                    principal: None,
                };
                manager_clone.generate_key(request).await
            });
            handles.push(handle);
        }

        // Wait for all to complete
        for handle in handles {
            let result = handle.await.unwrap();
            assert!(result.is_ok());
        }

        // Verify all keys were generated
        let stats = manager.get_stats().await.unwrap();
        assert_eq!(stats.total_generated, 10);
    }
}
