revoke-cli 0.3.0

Command-line interface for managing Revoke microservices infrastructure
use anyhow::{Result, Context};
use colored::Colorize;
use std::collections::HashMap;
use revoke_core::ConfigProvider;
use revoke_config::ConfigChange as ConfigChangeType;
use crate::config::Config;
use crate::commands::ConfigCommands;
use crate::utils::{print_success, print_error, print_info};
use futures::StreamExt;

pub async fn handle_command(command: ConfigCommands, config: &Config) -> Result<()> {
    let provider = create_config_provider(config).await?;
    
    match command {
        ConfigCommands::Get { key } => {
            get_config(&provider, &key).await?
        }
        ConfigCommands::Set { key, value } => {
            set_config(&provider, &key, &value).await?
        }
        ConfigCommands::Delete { key } => {
            delete_config(&provider, &key).await?
        }
        ConfigCommands::List { prefix } => {
            list_configs(&provider, prefix).await?
        }
        ConfigCommands::Watch { key } => {
            watch_config(&provider, &key).await?
        }
        ConfigCommands::Export { output, format } => {
            export_config(&provider, output, &format).await?
        }
        ConfigCommands::Import { file, format } => {
            import_config(&provider, &file, format).await?
        }
    }
    
    Ok(())
}

async fn create_config_provider(config: &Config) -> Result<Box<dyn ConfigProvider>> {
    use revoke_config::{ConsulConfigProvider, consul::ConsulConfigOptions};
    use std::time::Duration;
    
    let options = ConsulConfigOptions {
        address: config.consul_address.clone(),
        namespace: Some(config.config_namespace.clone()),
        watch_interval: Duration::from_secs(5),
        ..Default::default()
    };
    
    let provider = ConsulConfigProvider::new(options);
    
    // Start background watch loop
    let provider_clone = provider.clone();
    tokio::spawn(async move {
        provider_clone.start_watch_loop().await;
    });
    
    Ok(Box::new(provider))
}

async fn get_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
    match provider.get(key).await {
        Ok(value) => {
            println!("{}: {}", key.bold(), value);
        }
        Err(_) => {
            print_error(&format!("Configuration key '{}' not found", key));
        }
    }
    Ok(())
}

async fn set_config(provider: &Box<dyn ConfigProvider>, key: &str, value: &str) -> Result<()> {
    provider.set(key, value).await?;
    print_success(&format!("Configuration '{}' set successfully", key));
    Ok(())
}

async fn delete_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
    // Since ConfigProvider trait doesn't have delete, we'll set empty value
    provider.set(key, "").await?;
    print_success(&format!("Configuration '{}' deleted successfully", key));
    Ok(())
}

async fn list_configs(provider: &Box<dyn ConfigProvider>, prefix: Option<String>) -> Result<()> {
    print_info("Listing configurations from Consul...");
    
    // Since the trait doesn't have list method, we'll show a message
    if let Some(prefix) = prefix {
        print_info(&format!("Please use Consul UI to list configurations with prefix: {}", prefix));
    } else {
        print_info("Please use Consul UI at http://localhost:8500 to list all configurations");
    }
    
    Ok(())
}

async fn watch_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
    print_info(&format!("Watching configuration key: {}", key));
    println!("Press Ctrl+C to stop watching\n");
    
    let mut stream = provider.watch(key).await?;
    
    while let Some(value) = stream.next().await {
        println!("{} {} {}: {}", 
            chrono::Local::now().format("%H:%M:%S"),
            "CHANGED".yellow().bold(),
            key,
            value
        );
    }
    
    Ok(())
}

async fn export_config(
    provider: &Box<dyn ConfigProvider>,
    output: Option<String>,
    format: &str,
) -> Result<()> {
    print_info("Export is not directly supported through the ConfigProvider trait");
    print_info("Please use Consul's export features or API directly");
    
    Ok(())
}

async fn import_config(
    provider: &Box<dyn ConfigProvider>,
    file: &str,
    format: Option<String>,
) -> Result<()> {
    let content = std::fs::read_to_string(file)
        .context("Failed to read configuration file")?;
    
    let format = format.unwrap_or_else(|| {
        if file.ends_with(".json") {
            "json".to_string()
        } else if file.ends_with(".yaml") || file.ends_with(".yml") {
            "yaml".to_string()
        } else if file.ends_with(".toml") {
            "toml".to_string()
        } else {
            "json".to_string()
        }
    });
    
    let configs: HashMap<String, serde_json::Value> = match format.as_str() {
        "json" => serde_json::from_str(&content)?,
        "yaml" => serde_yaml::from_str(&content)?,
        "toml" => toml::from_str(&content)?,
        _ => return Err(anyhow::anyhow!("Unsupported import format: {}", format)),
    };
    
    let total = configs.len();
    let mut imported = 0;
    
    for (key, value) in configs {
        let value_str = match value {
            serde_json::Value::String(s) => s,
            v => v.to_string(),
        };
        
        match provider.set(&key, &value_str).await {
            Ok(_) => imported += 1,
            Err(e) => print_error(&format!("Failed to import '{}': {}", key, e)),
        }
    }
    
    print_success(&format!(
        "Imported {} out of {} configurations from {}",
        imported, total, file
    ));
    
    Ok(())
}

// Re-export ConfigChange for other modules
pub use revoke_config::ConfigChange;