//! Vault connectivity and basic functionality tests
//!
//! This module tests fundamental Vault operations:
//! - Server connectivity and authentication
//! - PKI secrets engine functionality
//! - AppRole authentication flow
//! - Basic certificate generation
//! - API response times and reliability

use anyhow::{Context, Result};
use serde_json::Value;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::process::Command as AsyncCommand;
use tracing::{debug, info, warn};

use crate::VaultTestConfig;

/// Test basic Vault server connectivity
pub async fn test_vault_status(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing Vault server status");
    
    let mut cmd = AsyncCommand::new("curl");
    cmd.args([
        "-s", "-f",
        &format!("{}/v1/sys/health", config.vault_addr)
    ]);

    let output = cmd.output().await
        .context("Failed to check Vault status")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Vault health check failed: {}", stderr));
    }

    let health_json = String::from_utf8_lossy(&output.stdout);
    let health: Value = serde_json::from_str(&health_json)
        .context("Failed to parse Vault health response")?;

    // Verify Vault is initialized and unsealed
    let initialized = health.get("initialized").and_then(|v| v.as_bool()).unwrap_or(false);
    let sealed = health.get("sealed").and_then(|v| v.as_bool()).unwrap_or(true);

    if !initialized {
        return Err(anyhow::anyhow!("Vault is not initialized"));
    }

    if sealed {
        return Err(anyhow::anyhow!("Vault is sealed"));
    }

    info!("Vault server is healthy and operational");
    Ok(start.elapsed())
}

/// Test Vault authentication with root token
pub async fn test_vault_authentication(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing Vault authentication");
    
    let mut cmd = AsyncCommand::new("curl");
    cmd.args([
        "-s", "-f",
        "-H", &format!("X-Vault-Token: {}", config.vault_token),
        &format!("{}/v1/auth/token/lookup-self", config.vault_addr)
    ]);

    let output = cmd.output().await
        .context("Failed to authenticate with Vault")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Vault authentication failed: {}", stderr));
    }

    let token_info = String::from_utf8_lossy(&output.stdout);
    let token_data: Value = serde_json::from_str(&token_info)
        .context("Failed to parse token info")?;

    // Verify token has necessary permissions
    if let Some(policies) = token_data.get("data").and_then(|d| d.get("policies")) {
        if !policies.as_array().unwrap_or(&Vec::new()).iter()
            .any(|p| p.as_str() == Some("root")) {
            return Err(anyhow::anyhow!("Token does not have root policy"));
        }
    }

    info!("Vault authentication successful");
    Ok(start.elapsed())
}

/// Test PKI secrets engine configuration
pub async fn test_pki_secrets_engine(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing PKI secrets engine");
    
    // List secrets engines
    let mut cmd = AsyncCommand::new("curl");
    cmd.args([
        "-s", "-f",
        "-H", &format!("X-Vault-Token: {}", config.vault_token),
        &format!("{}/v1/sys/mounts", config.vault_addr)
    ]);

    let output = cmd.output().await
        .context("Failed to list secrets engines")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Failed to list secrets engines: {}", stderr));
    }

    let mounts_json = String::from_utf8_lossy(&output.stdout);
    let mounts: Value = serde_json::from_str(&mounts_json)
        .context("Failed to parse mounts response")?;

    // Verify PKI engines are mounted
    let data = mounts.get("data").or_else(|| mounts.as_object().map(|_| &mounts))
        .context("No data in mounts response")?;

    let has_pki = data.get("pki/").is_some();
    let has_pki_int = data.get("pki_int/").is_some();

    if !has_pki || !has_pki_int {
        return Err(anyhow::anyhow!("PKI secrets engines not properly configured"));
    }

    info!("PKI secrets engines are properly configured");
    Ok(start.elapsed())
}

/// Test AppRole authentication method
pub async fn test_approle_authentication(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing AppRole authentication method");
    
    // List auth methods
    let mut cmd = AsyncCommand::new("curl");
    cmd.args([
        "-s", "-f",
        "-H", &format!("X-Vault-Token: {}", config.vault_token),
        &format!("{}/v1/sys/auth", config.vault_addr)
    ]);

    let output = cmd.output().await
        .context("Failed to list auth methods")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Failed to list auth methods: {}", stderr));
    }

    let auth_json = String::from_utf8_lossy(&output.stdout);
    let auth_methods: Value = serde_json::from_str(&auth_json)
        .context("Failed to parse auth methods response")?;

    // Verify AppRole is enabled
    let data = auth_methods.get("data").or_else(|| auth_methods.as_object().map(|_| &auth_methods))
        .context("No data in auth methods response")?;

    let has_approle = data.get("approle/").is_some();

    if !has_approle {
        return Err(anyhow::anyhow!("AppRole authentication method not enabled"));
    }

    // Test AppRole role exists
    let mut role_cmd = AsyncCommand::new("curl");
    role_cmd.args([
        "-s", "-f",
        "-H", &format!("X-Vault-Token: {}", config.vault_token),
        &format!("{}/v1/auth/approle/role/trading-services", config.vault_addr)
    ]);

    let role_output = role_cmd.output().await
        .context("Failed to check AppRole role")?;

    if !role_output.status.success() {
        return Err(anyhow::anyhow!("AppRole trading-services role not found"));
    }

    info!("AppRole authentication method is properly configured");
    Ok(start.elapsed())
}

/// Test basic certificate generation
pub async fn test_certificate_generation(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing certificate generation");
    
    let cert_request = serde_json::json!({
        "common_name": "test.foxhunt.internal",
        "ttl": "1h",
        "format": "pem"
    });

    let mut cmd = AsyncCommand::new("curl");
    cmd.args([
        "-s", "-f",
        "-X", "POST",
        "-H", &format!("X-Vault-Token: {}", config.vault_token),
        "-H", "Content-Type: application/json",
        "-d", &cert_request.to_string(),
        &format!("{}/v1/pki_int/issue/hft-trading", config.vault_addr)
    ]);

    let output = cmd.output().await
        .context("Failed to generate certificate")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("Certificate generation failed: {}", stderr));
    }

    let cert_json = String::from_utf8_lossy(&output.stdout);
    let cert_response: Value = serde_json::from_str(&cert_json)
        .context("Failed to parse certificate response")?;

    // Verify certificate data is present
    if let Some(data) = cert_response.get("data") {
        let has_certificate = data.get("certificate").and_then(|v| v.as_str()).is_some();
        let has_private_key = data.get("private_key").and_then(|v| v.as_str()).is_some();
        let has_ca_chain = data.get("ca_chain").is_some() || data.get("issuing_ca").is_some();

        if !has_certificate || !has_private_key || !has_ca_chain {
            return Err(anyhow::anyhow!("Certificate response missing required fields"));
        }

        // Verify certificate common name
        if let Some(cert_pem) = data.get("certificate").and_then(|v| v.as_str()) {
            if !cert_pem.contains("BEGIN CERTIFICATE") {
                return Err(anyhow::anyhow!("Invalid certificate format"));
            }
        }

        info!("Certificate generation successful");
    } else {
        return Err(anyhow::anyhow!("No data in certificate response"));
    }

    Ok(start.elapsed())
}

/// Test certificate with different parameters
pub async fn test_certificate_variations(config: &VaultTestConfig) -> Result<Duration> {
    let start = Instant::now();
    
    info!("Testing certificate generation with different parameters");

    let test_cases = vec![
        ("trading.foxhunt.internal", "30m"),
        ("backtesting.foxhunt.internal", "1h"),
        ("tli.foxhunt.internal", "2h"),
    ];

    for (common_name, ttl) in test_cases {
        let cert_request = serde_json::json!({
            "common_name": common_name,
            "ttl": ttl,
            "format": "pem",
            "alt_names": format!("*.{}", common_name)
        });

        let mut cmd = AsyncCommand::new("curl");
        cmd.args([
            "-s", "-f",
            "-X", "POST",
            "-H", &format!("X-Vault-Token: {}", config.vault_token),
            "-H", "Content-Type: application/json",
            "-d", &cert_request.to_string(),
            &format!("{}/v1/pki_int/issue/hft-trading", config.vault_addr)
        ]);

        let output = cmd.output().await
            .with_context(|| format!("Failed to generate certificate for {}", common_name))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(anyhow::anyhow!(
                "Certificate generation failed for {}: {}", common_name, stderr
            ));
        }

        debug!("Certificate generated successfully for {}", common_name);
    }

    info!("Certificate variation testing completed");
    Ok(start.elapsed())
}

/// Test Vault API response times
pub async fn test_api_response_times(config: &VaultTestConfig) -> Result<HashMap<String, Duration>> {
    info!("Testing Vault API response times");

    let mut response_times = HashMap::new();

    // Test various API endpoints
    let endpoints = vec![
        ("health", format!("{}/v1/sys/health", config.vault_addr)),
        ("auth", format!("{}/v1/auth/token/lookup-self", config.vault_addr)),
        ("pki_ca", format!("{}/v1/pki_int/ca/pem", config.vault_addr)),
    ];

    for (name, url) in endpoints {
        let start = Instant::now();
        
        let mut cmd = AsyncCommand::new("curl");
        cmd.args(["-s", "-f"]);
        
        if name != "health" && name != "pki_ca" {
            cmd.args(["-H", &format!("X-Vault-Token: {}", config.vault_token)]);
        }
        
        cmd.arg(&url);

        let output = cmd.output().await
            .with_context(|| format!("Failed to test endpoint: {}", name))?;

        let duration = start.elapsed();
        
        if !output.status.success() {
            warn!("Endpoint {} failed", name);
        } else {
            response_times.insert(name.to_string(), duration);
            debug!("Endpoint {} response time: {:?}", name, duration);
        }
    }

    info!("API response time testing completed");
    Ok(response_times)
}

/// Run all connectivity tests
pub async fn run_connectivity_tests(config: &VaultTestConfig) -> Result<Duration> {
    let overall_start = Instant::now();
    
    info!("Running Vault connectivity tests");

    // Test 1: Vault status
    test_vault_status(config).await
        .context("Vault status test failed")?;

    // Test 2: Authentication
    test_vault_authentication(config).await
        .context("Vault authentication test failed")?;

    // Test 3: PKI secrets engine
    test_pki_secrets_engine(config).await
        .context("PKI secrets engine test failed")?;

    // Test 4: AppRole authentication
    test_approle_authentication(config).await
        .context("AppRole authentication test failed")?;

    // Test 5: Certificate generation
    test_certificate_generation(config).await
        .context("Certificate generation test failed")?;

    // Test 6: Certificate variations
    test_certificate_variations(config).await
        .context("Certificate variations test failed")?;

    // Test 7: API response times
    let response_times = test_api_response_times(config).await
        .context("API response times test failed")?;

    // Validate response times are acceptable
    for (endpoint, duration) in &response_times {
        if *duration > Duration::from_millis(500) {
            warn!("Endpoint {} response time {}ms exceeds 500ms", endpoint, duration.as_millis());
        }
    }

    info!("All Vault connectivity tests passed");
    Ok(overall_start.elapsed())
}

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

    #[tokio::test]
    async fn test_vault_connectivity_integration() {
        let config = VaultTestConfig::default();
        
        // This test requires a running Vault server
        match run_connectivity_tests(&config).await {
            Ok(duration) => {
                println!("Connectivity tests completed in {:?}", duration);
            }
            Err(e) => {
                println!("Connectivity tests failed (expected without Vault): {}", e);
            }
        }
    }

    #[test]
    fn test_config_values() {
        let config = VaultTestConfig::default();
        assert!(!config.vault_addr.is_empty());
        assert!(!config.vault_token.is_empty());
        assert!(config.test_timeout > Duration::ZERO);
    }
}