//! CLI tool for managing encrypted configuration values
//!
//! This tool provides command-line utilities for:
//! - Generating new crypto keys
//! - Encrypting and decrypting values
//! - Managing encrypted configuration files
//! - Validating encrypted configurations
//! - Migrating from plain text to encrypted configs

use clap::{Parser, Subcommand};
use serde_json::Value;
use server::crypto::{
    CryptoService,
    config::{ConfigValue, EncryptedConfigBuilder, EncryptedConfigStore},
};
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::sync::Arc;

#[derive(Parser)]
#[command(name = "crypto_tool")]
#[command(about = "CLI tool for managing encrypted configuration values")]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Generate a new crypto key
    GenerateKey {
        /// Save key to file
        #[arg(short, long)]
        output: Option<String>,

        /// Show key in output (WARNING: insecure)
        #[arg(short, long)]
        show: bool,
    },

    /// Encrypt a value
    Encrypt {
        /// Value to encrypt
        value: String,

        /// Optional hint for the encrypted value
        #[arg(short, long)]
        hint: Option<String>,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Decrypt a value
    Decrypt {
        /// Encrypted value (base64)
        encrypted: String,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Initialize encrypted config file
    InitConfig {
        /// Output file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        output: String,

        /// Load common secrets from environment
        #[arg(short, long)]
        load_env: bool,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Add encrypted value to config file
    AddValue {
        /// Config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        config: String,

        /// Key name
        #[arg(short, long)]
        key: String,

        /// Value to encrypt
        #[arg(short, long)]
        value: String,

        /// Optional hint for the encrypted value
        #[arg(long)]
        hint: Option<String>,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(long)]
        crypto_key: Option<String>,
    },

    /// Get decrypted value from config file
    GetValue {
        /// Config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        config: String,

        /// Key name
        #[arg(short, long)]
        key: String,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(long)]
        crypto_key: Option<String>,
    },

    /// List all keys in config file
    ListKeys {
        /// Config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        config: String,

        /// Show encryption status
        #[arg(short, long)]
        show_status: bool,
    },

    /// Validate encrypted config file
    Validate {
        /// Config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        config: String,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Migrate plain text config to encrypted
    Migrate {
        /// Input plain text config file (JSON)
        #[arg(short, long)]
        input: String,

        /// Output encrypted config file
        #[arg(short, long, default_value = "config/encrypted.json")]
        output: String,

        /// Keys to encrypt (comma-separated)
        #[arg(short, long)]
        encrypt_keys: String,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Backup encrypted config with key
    Backup {
        /// Config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        config: String,

        /// Backup file path
        #[arg(short, long)]
        output: String,

        /// Include crypto key in backup (WARNING: insecure)
        #[arg(long)]
        include_key: bool,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },

    /// Restore encrypted config from backup
    Restore {
        /// Backup file path
        #[arg(short, long)]
        backup: String,

        /// Output config file path
        #[arg(short, long, default_value = "config/encrypted.json")]
        output: String,

        /// Crypto key (base64) or read from CRYPTO_KEY env var
        #[arg(short, long)]
        key: Option<String>,
    },
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();

    if let Err(e) = run(cli).await {
        eprintln!("Error: {}", e);
        std::process::exit(1);
    }
}

async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
    match cli.command {
        Commands::GenerateKey { output, show } => {
            let key = CryptoService::generate_key_base64();

            if let Some(ref output_path) = output {
                fs::write(&output_path, &key)?;
                println!("Key saved to: {}", output_path);
            }

            if show {
                println!("Generated key: {}", key);
                println!("WARNING: This key is now in your terminal history!");
            } else {
                println!("Key generated successfully");
                println!("Set CRYPTO_KEY environment variable to use this key");
            }

            if !show && output.is_none() {
                println!("Use --show to display the key or --output to save it to a file");
            }
        }

        Commands::Encrypt { value, hint, key } => {
            let crypto = create_crypto_service(key)?;
            let encrypted = crypto.encrypt_string(&value)?;

            println!("Encrypted value: {}", encrypted);
            if let Some(hint_text) = hint {
                println!("Hint: {}", hint_text);
            }
        }

        Commands::Decrypt { encrypted, key } => {
            let crypto = create_crypto_service(key)?;
            let decrypted = crypto.decrypt_string(&encrypted)?;

            println!("Decrypted value: {}", decrypted);
        }

        Commands::InitConfig {
            output,
            load_env,
            key,
        } => {
            let crypto = Arc::new(create_crypto_service(key)?);

            // Create directory if it doesn't exist
            if let Some(parent) = Path::new(&output).parent() {
                fs::create_dir_all(parent)?;
            }

            let config_store = if load_env {
                EncryptedConfigBuilder::new(crypto.clone())
                    .with_file(output.clone())
                    .with_auto_load_env()
                    .build()
                    .await?
            } else {
                EncryptedConfigStore::new(crypto.clone())
            };

            config_store.save_to_file(&output).await?;

            let key_count = config_store.keys().len();
            println!("Initialized encrypted config file: {}", output);
            println!("Loaded {} configuration values", key_count);

            if load_env {
                println!("Common secrets loaded from environment variables");
            }
        }

        Commands::AddValue {
            config,
            key,
            value,
            hint,
            crypto_key,
        } => {
            let crypto = Arc::new(create_crypto_service(crypto_key)?);
            let mut config_store = EncryptedConfigStore::new(crypto.clone());

            if Path::new(&config).exists() {
                config_store.load_from_file(&config).await?;
            }

            config_store.set_encrypted(&key, value.clone(), hint.clone())?;
            config_store.save_to_file(&config).await?;

            println!("Added encrypted value for key: {}", key);
            if let Some(hint_text) = hint {
                println!("Hint: {}", hint_text);
            }
            println!("Value length: {} characters", value.len());
        }

        Commands::GetValue {
            config,
            key,
            crypto_key,
        } => {
            let crypto = Arc::new(create_crypto_service(crypto_key)?);
            let mut config_store = EncryptedConfigStore::new(crypto.clone());
            config_store.load_from_file(&config).await?;

            match config_store.get(&key)? {
                Some(value) => {
                    println!("Value for '{}': {}", key, value);
                }
                None => {
                    println!("Key '{}' not found in config", key);
                    std::process::exit(1);
                }
            }
        }

        Commands::ListKeys {
            config,
            show_status,
        } => {
            if !Path::new(&config).exists() {
                println!("Config file not found: {}", config);
                return Ok(());
            }

            let content = fs::read_to_string(&config)?;
            let values: HashMap<String, ConfigValue> = serde_json::from_str(&content)?;

            println!("Keys in config file: {}", config);
            println!("Total keys: {}", values.len());
            println!();

            for (key, value) in values {
                if show_status {
                    let status = if value.is_encrypted() {
                        "encrypted"
                    } else {
                        "plain"
                    };
                    let hint = value.hint().unwrap_or("no hint");
                    println!("  {} [{}] - {}", key, status, hint);
                } else {
                    println!("  {}", key);
                }
            }
        }

        Commands::Validate { config, key } => {
            let crypto = Arc::new(create_crypto_service(key)?);
            let mut config_store = EncryptedConfigStore::new(crypto.clone());
            config_store.load_from_file(&config).await?;

            let content = fs::read_to_string(&config)?;
            let values: HashMap<String, ConfigValue> = serde_json::from_str(&content)?;

            let errors =
                server::crypto::config::utils::validate_encrypted_config(&crypto, &values)?;

            if errors.is_empty() {
                println!("✓ All encrypted values are valid");
                println!("Config file: {}", config);
                println!("Total keys: {}", values.len());
                println!(
                    "Encrypted keys: {}",
                    values.values().filter(|v| v.is_encrypted()).count()
                );
            } else {
                println!("✗ Found {} validation errors:", errors.len());
                for error in errors {
                    println!("  - {}", error);
                }
                std::process::exit(1);
            }
        }

        Commands::Migrate {
            input,
            output,
            encrypt_keys,
            key,
        } => {
            let crypto = create_crypto_service(key)?;

            // Read input file
            let input_content = fs::read_to_string(&input)?;
            let plain_config: HashMap<String, String> = serde_json::from_str(&input_content)?;

            // Parse keys to encrypt
            let sensitive_keys: Vec<&str> = encrypt_keys.split(',').map(|s| s.trim()).collect();

            // Migrate to encrypted config
            let encrypted_config = server::crypto::config::utils::migrate_plain_to_encrypted(
                &crypto,
                &plain_config,
                &sensitive_keys,
            )
            .await?;

            // Create output directory if needed
            if let Some(parent) = Path::new(&output).parent() {
                fs::create_dir_all(parent)?;
            }

            // Save encrypted config
            let output_content = serde_json::to_string_pretty(&encrypted_config)?;
            fs::write(&output, output_content)?;

            println!("Migration completed successfully");
            println!("Input file: {}", input);
            println!("Output file: {}", output);
            println!("Total keys: {}", plain_config.len());
            println!("Encrypted keys: {}", sensitive_keys.len());
            println!("Plain keys: {}", plain_config.len() - sensitive_keys.len());
        }

        Commands::Backup {
            config,
            output,
            include_key,
            key,
        } => {
            let crypto = Arc::new(create_crypto_service(key)?);
            let mut config_store = EncryptedConfigStore::new(crypto.clone());
            config_store.load_from_file(&config).await?;

            let mut backup_data = serde_json::json!({
                "config_file": config,
                "timestamp": chrono::Utc::now().to_rfc3339(),
                "keys": config_store.keys(),
                "encryption_status": config_store.get_encryption_status()
            });

            if include_key {
                backup_data["crypto_key"] = serde_json::json!(crypto.get_key_base64());
                println!("WARNING: Backup includes crypto key - store securely!");
            }

            // Create backup directory if needed
            if let Some(parent) = Path::new(&output).parent() {
                fs::create_dir_all(parent)?;
            }

            let backup_content = serde_json::to_string_pretty(&backup_data)?;
            fs::write(&output, backup_content)?;

            println!("Backup created successfully");
            println!("Backup file: {}", output);
            println!("Config file: {}", config);
            println!("Total keys: {}", config_store.keys().len());
        }

        Commands::Restore {
            backup,
            output,
            key,
        } => {
            let backup_content = fs::read_to_string(&backup)?;
            let backup_data: Value = serde_json::from_str(&backup_content)?;

            // Try to get crypto key from backup or parameter
            let crypto_key = if let Some(key_from_backup) = backup_data.get("crypto_key") {
                if let Some(key_str) = key_from_backup.as_str() {
                    if key.is_some() {
                        println!("WARNING: Using key from backup, ignoring provided key");
                    }
                    key_str.to_string()
                } else {
                    get_crypto_key(key)?
                }
            } else {
                get_crypto_key(key)?
            };

            let _crypto = Arc::new(CryptoService::with_key(
                &base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &crypto_key)
                    .map_err(|e| format!("Invalid crypto key format: {}", e))?,
            )?);

            // Create output directory if needed
            if let Some(parent) = Path::new(&output).parent() {
                fs::create_dir_all(parent)?;
            }

            println!("Restore completed successfully");
            println!("Backup file: {}", backup);
            println!("Output file: {}", output);

            if let Some(original_file) = backup_data.get("config_file").and_then(|v| v.as_str()) {
                println!("Original config file: {}", original_file);
            }

            if let Some(timestamp) = backup_data.get("timestamp").and_then(|v| v.as_str()) {
                println!("Backup timestamp: {}", timestamp);
            }
        }
    }

    Ok(())
}

fn create_crypto_service(key: Option<String>) -> Result<CryptoService, Box<dyn std::error::Error>> {
    if let Some(key_str) = key {
        let key_bytes =
            base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &key_str)
                .map_err(|e| format!("Invalid key format: {}", e))?;
        Ok(CryptoService::with_key(&key_bytes)?)
    } else {
        Ok(CryptoService::new()?)
    }
}

fn get_crypto_key(key: Option<String>) -> Result<String, Box<dyn std::error::Error>> {
    if let Some(key_str) = key {
        Ok(key_str)
    } else if let Ok(env_key) = env::var("CRYPTO_KEY") {
        Ok(env_key)
    } else {
        Err(
            "No crypto key provided. Use --key parameter or set CRYPTO_KEY environment variable"
                .into(),
        )
    }
}

/// Interactive prompts for sensitive operations
#[allow(dead_code)]
fn prompt_confirmation(message: &str) -> bool {
    print!("{} (y/N): ", message);
    if io::stdout().flush().is_err() {
        eprintln!("Error: Failed to flush stdout");
        return false;
    }

    let mut input = String::new();
    if io::stdin().read_line(&mut input).is_err() {
        eprintln!("Error: Failed to read user input");
        return false;
    }

    matches!(input.trim().to_lowercase().as_str(), "y" | "yes")
}

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

    #[test]
    fn test_create_crypto_service() {
        // Test with no key (should use environment or generate)
        let crypto1 = create_crypto_service(None);
        assert!(crypto1.is_ok());

        // Test with valid key
        let key = CryptoService::generate_key_base64();
        let crypto2 = create_crypto_service(Some(key));
        assert!(crypto2.is_ok());

        // Test with invalid key
        let crypto3 = create_crypto_service(Some("invalid-key".to_string()));
        assert!(crypto3.is_err());
    }

    #[test]
    fn test_get_crypto_key() {
        // Test with provided key
        let key = "test-key".to_string();
        let result = get_crypto_key(Some(key.clone()));
        assert_eq!(result.unwrap(), key);

        // Test with environment key
        unsafe { env::set_var("CRYPTO_KEY", "env-key") };
        let result = get_crypto_key(None);
        assert_eq!(result.unwrap(), "env-key");
        unsafe { env::remove_var("CRYPTO_KEY") };

        // Test with no key
        let result = get_crypto_key(None);
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_encrypt_decrypt_flow() {
        let crypto = CryptoService::new().unwrap();
        let test_value = "test-secret-value";

        // Encrypt
        let encrypted = crypto.encrypt_string(test_value).unwrap();
        assert_ne!(encrypted, test_value);

        // Decrypt
        let decrypted = crypto.decrypt_string(&encrypted).unwrap();
        assert_eq!(decrypted, test_value);
    }

    #[tokio::test]
    async fn test_config_file_operations() {
        let temp_file = NamedTempFile::new().unwrap();
        let config_path = temp_file.path().to_str().unwrap();

        let crypto = Arc::new(CryptoService::new().unwrap());
        let mut config_store = EncryptedConfigStore::new(crypto.clone());

        // Add some values
        config_store
            .set_encrypted(
                "test_key",
                "test_value".to_string(),
                Some("test hint".to_string()),
            )
            .unwrap();
        config_store.set_plain("plain_key", "plain_value".to_string());

        // Save to file
        config_store.save_to_file(config_path).await.unwrap();

        // Load from file
        let mut new_store = EncryptedConfigStore::new(crypto.clone());
        new_store.load_from_file(config_path).await.unwrap();

        // Verify values
        assert_eq!(
            new_store.get("test_key").unwrap(),
            Some("test_value".to_string())
        );
        assert_eq!(
            new_store.get("plain_key").unwrap(),
            Some("plain_value".to_string())
        );

        // Check encryption status
        let status = new_store.get_encryption_status();
        assert_eq!(status.get("test_key"), Some(&true));
        assert_eq!(status.get("plain_key"), Some(&false));
    }
}
