porta-rs 0.1.1

Zero-trust CGNAT bypass via WireGuard tunnel
use anyhow::Result;
use clap::Args;
use crate::config::load_config;
use crate::metrics::storage::MetricsStorage;

#[derive(Args)]
pub struct MetricsArgs {
    /// Show live metrics with updates
    #[arg(short, long)]
    pub live: bool,

    /// Output in JSON format
    #[arg(short, long)]
    pub json: bool,

    /// Update interval in seconds (for live mode)
    #[arg(short, long, default_value = "1")]
    pub interval: u64,
}

pub fn execute(args: &MetricsArgs) -> Result<()> {
    let config = load_config()?;
    let data_dir = match config.role() {
        crate::config::Role::Client => {
            dirs::config_dir()
                .unwrap_or_else(|| std::path::PathBuf::from("."))
                .join("porta")
                .to_string_lossy()
                .to_string()
        }
        crate::config::Role::Server => "/var/lib/porta".to_string(),
    };

    let storage = MetricsStorage::new(&data_dir);
    let snapshots = storage.load()?;

    println!("\n\x1b[1;36m╔══════════════════════════════════════════════════════════════╗\x1b[0m");
    println!("\x1b[1;36m║                    PORTA METRICS (v{})                       ║\x1b[0m", env!("CARGO_PKG_VERSION"));
    println!("\x1b[1;36m╚══════════════════════════════════════════════════════════════╝\x1b[0m\n");

    // Show config info
    println!("  \x1b[1;33mRole:\x1b[0m       {:?}", config.role());
    println!("  \x1b[1;33mInterface:\x1b[0m  {}", config.interface());

    match config.role() {
        crate::config::Role::Client => {
            println!("  \x1b[1;33mServer:\x1b[0m     {}:{}", config.server_address(), config.server_port());
            println!("  \x1b[1;33mPorts:\x1b[0m      {}", config.ports().len());
        }
        crate::config::Role::Server => {
            println!("  \x1b[1;33mListen:\x1b[0m     port {}", config.listen_port());
        }
    }

    println!();

    if args.live {
        println!("  \x1b[1;36mLive mode not yet implemented. Use: journalctl -u porta -f\x1b[0m\n");
    }

    // Show metrics from storage
    if let Some(latest) = snapshots.last() {
        if args.json {
            println!("{}", serde_json::to_string_pretty(latest)?);
        } else {
            println!("  \x1b[1;33mLast Metrics:\x1b[0m");
            println!("    Updated:      {}", latest.timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
            println!("    Bytes sent:   {}", format_bytes(latest.bytes_sent));
            println!("    Bytes recv:   {}", format_bytes(latest.bytes_received));
            println!("    Packets sent: {}", latest.packets_sent);
            println!("    Packets recv: {}", latest.packets_received);
            println!("    Connections:  {}", latest.active_connections);
            if let Some(latency) = latest.latency_ms {
                println!("    Latency:      {:.2} ms", latency);
            }
            println!("    Data points:  {}", snapshots.len());
        }
    } else {
        println!("  \x1b[1;33mNo metrics data yet.\x1b[0m");
        println!("  Metrics will appear after the service runs for a while.");
    }

    println!();
    Ok(())
}

fn format_bytes(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = 1024 * KB;
    const GB: u64 = 1024 * MB;

    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)
    }
}