firecloud-cli 0.2.0

Command-line interface for FireCloud P2P messaging and file sharing
use anyhow::{Context, Result};
use colored::Colorize;
use dialoguer::{Confirm, Input};
use std::path::PathBuf;
use firecloud_net::FireCloudNode;
use crate::config::FireCloudConfig;

pub async fn run(data_dir: PathBuf) -> Result<()> {
    println!("\n{}", "🚀 Welcome to FireCloud".bold().cyan());
    println!("{}", "─".repeat(50).dimmed());
    
    // Check if already initialized
    let config_path = data_dir.join("config.toml");
    if config_path.exists() {
        let reinit = Confirm::new()
            .with_prompt("FireCloud is already initialized. Re-initialize?")
            .default(false)
            .interact()?;
        
        if !reinit {
            println!("\n{}", "✓ Setup cancelled".yellow());
            return Ok(());
        }
    }
    
    // Create data directory
    std::fs::create_dir_all(&data_dir)
        .context("Failed to create data directory")?;
    
    println!("\n{} Initializing FireCloud...", "âš™".cyan());
    
    // Create subdirectories
    std::fs::create_dir_all(data_dir.join("chunks"))?;
    std::fs::create_dir_all(data_dir.join("manifests"))?;
    
    // Generate identity using crypto module
    use firecloud_crypto::KeyPair;
    let keypair = KeyPair::generate();
    let public_key = keypair.public_key_bytes();
    
    // Save identity
    let identity_path = data_dir.join("identity.key");
    if !identity_path.exists() {
        std::fs::write(&identity_path, keypair.to_bytes())?;
    }
    
    println!("{} Identity created: {}", "✓".green(), hex::encode(public_key).yellow());
    println!("{} Data directory created: {}", "✓".green(), data_dir.display().to_string().dimmed());
    
    // Ask if user wants to provide storage
    println!("\n{}", "Storage Provider Setup".bold());
    let provide_storage = Confirm::new()
        .with_prompt("Would you like to provide storage to the network?")
        .default(false)
        .interact()?;
    
    let mut config = FireCloudConfig::default();
    
    if provide_storage {
        // Ask for storage quota
        let quota_str: String = Input::new()
            .with_prompt("How much storage would you like to provide? (e.g., 10GB, 500MB)")
            .default("10GB".to_string())
            .interact_text()?;
        
        // Parse the quota
        let quota_bytes = parse_size(&quota_str)
            .context("Invalid storage size format. Use formats like: 10GB, 500MB, 1TB")?;
        
        config.storage_quota = Some(quota_bytes);
        
        println!("\n{} Storage reservation: {} allocated", 
                 "✓".green(), 
                 format_size(quota_bytes).yellow());
        
        // Check available disk space
        match check_disk_space(&data_dir, quota_bytes) {
            Ok(available) => {
                println!("{} Available disk space: {}", 
                         "ℹ".blue(), 
                         format_size(available).dimmed());
            }
            Err(e) => {
                eprintln!("{} Warning: Could not check disk space: {}", "âš ".yellow(), e);
            }
        }
    } else {
        println!("\n{} You can enable storage sharing later using: {}", 
                 "ℹ".blue(), 
                 "firecloud mod".cyan());
    }
    
    // Save configuration
    config.save(&config_path)
        .context("Failed to save configuration")?;
    
    println!("\n{}", "─".repeat(50).dimmed());
    println!("{}", "🎉 Setup complete!".bold().green());
    println!("\n{}", "Next steps:".bold());
    println!("  • Upload a file: {}", "firecloud upload <file>".cyan());
    println!("  • View network: {}", "firecloud network".cyan());
    if provide_storage {
        println!("  • Check usage: {}", "firecloud usage".cyan());
    }
    println!();
    
    Ok(())
}

/// Parse size string like "10GB", "500MB" to bytes
fn parse_size(s: &str) -> Result<u64> {
    let s = s.trim().to_uppercase();
    
    let (num_str, multiplier) = if s.ends_with("TB") {
        (&s[..s.len()-2], 1_000_000_000_000u64)
    } else if s.ends_with("GB") {
        (&s[..s.len()-2], 1_000_000_000u64)
    } else if s.ends_with("MB") {
        (&s[..s.len()-2], 1_000_000u64)
    } else if s.ends_with("KB") {
        (&s[..s.len()-2], 1_000u64)
    } else if s.ends_with('B') {
        (&s[..s.len()-1], 1u64)
    } else {
        (s.as_str(), 1u64)
    };
    
    let num: f64 = num_str.trim().parse()
        .context("Invalid number in size specification")?;
    
    Ok((num * multiplier as f64) as u64)
}

/// Format bytes to human-readable string
fn format_size(bytes: u64) -> String {
    const KB: u64 = 1_000;
    const MB: u64 = 1_000_000;
    const GB: u64 = 1_000_000_000;
    const TB: u64 = 1_000_000_000_000;
    
    if bytes >= TB {
        format!("{:.2} TB", bytes as f64 / TB as f64)
    } else if bytes >= GB {
        format!("{:.2} GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.2} MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.2} KB", bytes as f64 / KB as f64)
    } else {
        format!("{} B", bytes)
    }
}

/// Check available disk space at the given path
fn check_disk_space(path: &PathBuf, _required: u64) -> Result<u64> {
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        let metadata = std::fs::metadata(path)
            .or_else(|_| std::fs::metadata(path.parent().unwrap_or(path)))?;
        
        // On Unix, we can use statvfs to get filesystem stats
        // For simplicity, we'll just return a placeholder
        // In production, use sys-info or a similar crate
        Ok(metadata.blocks() * 512) // Approximate
    }
    
    #[cfg(not(unix))]
    {
        // On Windows, would use GetDiskFreeSpaceEx
        // For now, return a large number
        Ok(1_000_000_000_000) // 1TB placeholder
    }
}