//! ONCP Master Node - CDN Control Plane CLI & API Server
//!
//! Provides:
//! - REST API for node and user management
//! - CLI dashboard for monitoring
//! - Dynamic SNI distribution
//! - Network health monitoring

use anyhow::Result;
use clap::{Parser, Subcommand};
use console::{style, Term};
use indicatif::{ProgressBar, ProgressStyle};
use oncp::{AppState, Node, User, run_server};
use std::sync::Arc;
use std::time::Duration;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[derive(Parser)]
#[command(name = "oncp-master")]
#[command(about = "OSTP CDN Control Plane - Master Node", long_about = None)]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
    
    /// Database path
    #[arg(short, long, default_value = "oncp.db", global = true)]
    database: String,
    
    /// Log level (error, warn, info, debug, trace)
    #[arg(long, default_value = "info", global = true)]
    log_level: String,
}

#[derive(Subcommand)]
enum Commands {
    /// Start the API server
    Serve {
        /// Listen address
        #[arg(short, long, default_value = "0.0.0.0:8080")]
        listen: String,
        
        /// Network octet for 10.X.0.0/16 subnet (0-255)
        #[arg(short = 'n', long, default_value = "42")]
        network_octet: u8,
    },
    
    /// Interactive dashboard
    Dashboard,
    
    /// Node management commands
    Node {
        #[command(subcommand)]
        action: NodeCommands,
    },
    
    /// User management commands
    User {
        #[command(subcommand)]
        action: UserCommands,
    },
    
    /// SNI management commands
    Sni {
        #[command(subcommand)]
        action: SniCommands,
    },
    
    /// Show network statistics
    Stats,
}

#[derive(Subcommand)]
enum NodeCommands {
    /// List all nodes
    List,
    /// Add a new node
    Add {
        /// Node name
        #[arg(short, long)]
        name: String,
        /// Node address (ip:port)
        #[arg(short, long)]
        address: String,
        /// Country code
        #[arg(short, long)]
        country: String,
    },
    /// Remove a node
    Remove {
        /// Node ID
        id: String,
    },
    /// List pending enrollment requests
    Pending,
    /// Approve enrollment request
    Approve {
        /// Node ID to approve
        id: String,
    },
    /// Reject enrollment request
    Reject {
        /// Node ID to reject
        id: String,
    },
    /// Generate enrollment token
    Token {
        /// Token expiry duration in minutes
        #[arg(long, default_value = "3")]
        expiry: i64,
    },
}

#[derive(Subcommand)]
enum UserCommands {
    /// List all users
    List,
    /// Create a new user
    Create {
        /// Bandwidth quota in GB
        #[arg(short, long, default_value = "100")]
        quota: u64,
        /// Validity in days
        #[arg(short, long, default_value = "30")]
        days: i64,
    },
    /// Show user config (for client setup)
    Config {
        /// User UUID
        id: String,
    },
    /// Delete a user
    Delete {
        /// User UUID
        id: String,
    },
}

#[derive(Subcommand)]
enum SniCommands {
    /// List active SNIs
    List,
    /// Block a domain
    Block {
        /// Domain to block
        domain: String,
    },
    /// Add a domain
    Add {
        /// Domain to add
        domain: String,
        /// Country code
        #[arg(short, long, default_value = "GLOBAL")]
        country: String,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();
    
    // Initialize logging
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::new(&cli.log_level))
        .with(tracing_subscriber::fmt::layer())
        .init();
    
    let state = Arc::new(AppState::new(&cli.database, 42)?);  // Default network octet
    
    // Initialize default SNIs
    state.sni_manager.init_defaults().await;
    
    // Start background token cleanup
    let cleanup_state = state.clone();
    tokio::spawn(async move {
        loop {
            tokio::time::sleep(Duration::from_secs(60)).await;
            cleanup_state.token_manager.cleanup_expired();
        }
    });
    
    match cli.command {
        Commands::Serve { listen, network_octet } => {
            // Update state with correct network octet
            let state = Arc::new(AppState::new(&cli.database, network_octet)?);
            state.sni_manager.init_defaults().await;
            
            // Start token cleanup for this state too
            let cleanup_state = state.clone();
            tokio::spawn(async move {
                loop {
                    tokio::time::sleep(Duration::from_secs(60)).await;
                    cleanup_state.token_manager.cleanup_expired();
                }
            });
            
            run_api_server(state, &listen).await
        }
        Commands::Dashboard => {
            run_dashboard(state).await
        }
        Commands::Node { action } => {
            handle_node_command(state, action).await
        }
        Commands::User { action } => {
            handle_user_command(state, action).await
        }
        Commands::Sni { action } => {
            handle_sni_command(state, action).await
        }
        Commands::Stats => {
            show_stats(state).await
        }
    }
}

async fn run_api_server(state: Arc<AppState>, listen: &str) -> Result<()> {
    println!("{}", style("🚀 Starting ONCP Master Node").green().bold());
    println!("   {} API listening on {}", style("→").cyan(), style(listen).yellow());
    println!("   {} Database: {}", style("→").cyan(), style("oncp.db").yellow());
    println!();
    
    // Start stale cleanup task
    let cleanup_state = state.clone();
    tokio::spawn(async move {
        loop {
            tokio::time::sleep(Duration::from_secs(30)).await;
            let stale = cleanup_state.nodes.cleanup_stale().await;
            if !stale.is_empty() {
                tracing::info!("Marked {} nodes as offline (stale)", stale.len());
            }
        }
    });
    
    run_server(state, listen).await
}

async fn run_dashboard(state: Arc<AppState>) -> Result<()> {
    let term = Term::stdout();
    
    loop {
        term.clear_screen()?;
        
        // Header
        println!("{}", style("╔════════════════════════════════════════════════════════════════╗").cyan());
        println!("{}", style("║           OSTP CDN Control Plane - Live Dashboard              ║").cyan().bold());
        println!("{}", style("╚════════════════════════════════════════════════════════════════╝").cyan());
        println!();
        
        // Network stats
        let stats = state.nodes.network_stats().await;
        
        println!("{}", style("📊 Network Status").green().bold());
        println!("   ├─ Nodes: {} / {} online", 
            style(stats.online_nodes).green().bold(),
            style(stats.total_nodes).dim());
        println!("   ├─ Connections: {}", style(stats.total_connections).yellow().bold());
        println!("   ├─ Traffic TX: {:.2} MB", stats.total_bytes_tx as f64 / 1024.0 / 1024.0);
        println!("   ├─ Traffic RX: {:.2} MB", stats.total_bytes_rx as f64 / 1024.0 / 1024.0);
        println!("   └─ Avg Load: {:.1}%", stats.avg_load * 100.0);
        println!();
        
        // Node list
        let nodes = state.nodes.list().await;
        println!("{}", style("🖥️  Nodes").green().bold());
        if nodes.is_empty() {
            println!("   └─ {}", style("No nodes registered").dim());
        } else {
            for (i, node) in nodes.iter().enumerate() {
                let status_icon = match node.status {
                    oncp::NodeStatus::Online => style("●").green(),
                    oncp::NodeStatus::Offline => style("●").red(),
                    oncp::NodeStatus::Maintenance => style("●").yellow(),
                    oncp::NodeStatus::Overloaded => style("●").magenta(),
                };
                let prefix = if i == nodes.len() - 1 { "└─" } else { "├─" };
                println!("   {} {} {} ({}) - {} conns, {:.0}% load",
                    prefix,
                    status_icon,
                    style(&node.name).bold(),
                    node.country_code,
                    node.active_connections,
                    node.cpu_load * 100.0
                );
            }
        }
        println!();
        
        // SNI stats
        let sni_stats = state.sni_manager.stats().await;
        println!("{}", style("🔗 SNI Configuration").green().bold());
        println!("   ├─ Active Domains: {}", sni_stats.total_domains);
        println!("   ├─ Blocked Domains: {}", style(sni_stats.blocked_domains).red());
        println!("   ├─ Countries: {}", sni_stats.countries);
        println!("   └─ Pending Updates: {}", sni_stats.pending_updates);
        println!();
        
        // Controls
        println!("{}", style("─────────────────────────────────────────────").dim());
        println!("Press {} to refresh, {} to quit", 
            style("Enter").cyan().bold(),
            style("Ctrl+C").red().bold());
        
        // Wait for input or timeout
        tokio::select! {
            _ = tokio::time::sleep(Duration::from_secs(5)) => {}
            _ = tokio::signal::ctrl_c() => {
                println!("\n{}", style("Goodbye!").green());
                return Ok(());
            }
        }
    }
}

async fn handle_node_command(state: Arc<AppState>, action: NodeCommands) -> Result<()> {
    match action {
        NodeCommands::List => {
            let nodes = state.nodes.list().await;
            
            println!("{}", style("Registered Nodes").green().bold());
            println!("{}", style("─").dim().to_string().repeat(70));
            
            if nodes.is_empty() {
                println!("{}", style("No nodes registered").dim());
            } else {
                println!("{:<36} {:<15} {:<8} {:<6} {:<8}",
                    style("ID").bold(),
                    style("Name").bold(),
                    style("Country").bold(),
                    style("Status").bold(),
                    style("Load").bold()
                );
                
                for node in nodes {
                    let status = match node.status {
                        oncp::NodeStatus::Online => style("ONLINE").green(),
                        oncp::NodeStatus::Offline => style("OFFLINE").red(),
                        oncp::NodeStatus::Maintenance => style("MAINT").yellow(),
                        oncp::NodeStatus::Overloaded => style("OVERLOAD").magenta(),
                    };
                    
                    println!("{:<36} {:<15} {:<8} {:<6} {:.1}%",
                        node.node_id,
                        node.name,
                        node.country_code,
                        status,
                        node.cpu_load * 100.0
                    );
                }
            }
        }
        
        NodeCommands::Add { name, address, country } => {
            let node = Node::new(&name, &address, &country);
            let id = state.nodes.register(node).await;
            
            println!("{} Node registered", style("✓").green().bold());
            println!("  ID: {}", style(id).yellow());
            println!("  Name: {}", name);
            println!("  Address: {}", address);
            println!("  Country: {}", country);
        }
        
        NodeCommands::Remove { id } => {
            let uuid = uuid::Uuid::parse_str(&id)?;
            match state.nodes.remove(&uuid).await {
                Some(node) => {
                    println!("{} Removed node: {}", style("✓").green().bold(), node.name);
                }
                None => {
                    println!("{} Node not found", style("✗").red().bold());
                }
            }
        }
        
        NodeCommands::Pending => {
            use oncp::EnrollmentState;
            
            let pending = state.enrollment.list_by_state(EnrollmentState::Pending)?;
            
            println!("{}", style("Pending Enrollment Requests").green().bold());
            println!("{}", style("─").dim().to_string().repeat(80));
            
            if pending.is_empty() {
                println!("{}", style("No pending requests").dim());
            } else {
                println!("{:<36} {:<15} {:<20} {:<8}",
                    style("Node ID").bold(),
                    style("Name").bold(),
                    style("Address").bold(),
                    style("Country").bold()
                );
                
                for node in pending {
                    println!("{:<36} {:<15} {:<20} {:<8}",
                        node.node_id,
                        node.name,
                        node.address,
                        node.country_code
                    );
                }
                
                println!();
                println!("{}", style("Use 'node approve <id>' to approve").dim());
            }
        }
        
        NodeCommands::Approve { id } => {
            let uuid = uuid::Uuid::parse_str(&id)?;
            
            // Allocate IP from IPAM pool
            let assigned_ip = state.ipam_pool.lock().allocate()
                .map_err(|e| anyhow::anyhow!("Failed to allocate IP: {}", e))?;
            
            match state.enrollment.approve(&uuid, &assigned_ip.to_string()) {
                Ok(node_psk) => {
                    println!("{} Node approved", style("✓").green().bold());
                    println!();
                    println!("  Node ID:     {}", style(uuid).yellow());
                    println!("  Assigned IP: {}", style(&assigned_ip).cyan().bold());
                    println!("  Node PSK:    {}", style(&node_psk).yellow().bold());
                    println!();
                    println!("{}", style("⚠ IMPORTANT: Save this PSK securely!").red().bold());
                    println!("{}", style("  Send it to the node operator via secure channel").dim());
                    println!("{}", style("  The node must use this PSK to connect").dim());
                }
                Err(e) => {
                    // Release IP on failure
                    let _ = state.ipam_pool.lock().release(assigned_ip);
                    println!("{} Failed to approve: {}", style("✗").red().bold(), e);
                }
            }
        }
        
        NodeCommands::Reject { id } => {
            let uuid = uuid::Uuid::parse_str(&id)?;
            
            match state.enrollment.reject(&uuid) {
                Ok(()) => {
                    println!("{} Node enrollment rejected", style("✓").green().bold());
                }
                Err(e) => {
                    println!("{} Failed to reject: {}", style("✗").red().bold(), e);
                }
            }
        }
        
        NodeCommands::Token { expiry } => {
            let token = state.token_manager.generate(expiry);
            
            println!("{} Enrollment token generated", style("✓").green().bold());
            println!();
            println!("  Token:   {}", style(&token).yellow().bold());
            println!("  Expires: {} minutes", style(expiry).cyan());
            println!();
            println!("{}", style("⚠ IMPORTANT: This token can only be used once!").red().bold());
            println!("{}", style("  Send it to the node operator via secure channel").dim());
            println!("{}", style("  Token will expire and be deleted after use").dim());
        }
    }
    
    Ok(())
}

async fn handle_user_command(state: Arc<AppState>, action: UserCommands) -> Result<()> {
    match action {
        UserCommands::List => {
            
            
            println!("{}", style("Registered Users").green().bold());
            println!("{}", style("─").dim().to_string().repeat(80));
            
            // Query directly since we need list functionality
            let conn = state.users.conn.lock().unwrap();
            let mut stmt = conn.prepare(
                "SELECT uuid, bandwidth_quota, bandwidth_used, expires_at, active FROM users"
            )?;
            
            let users = stmt.query_map([], |row| {
                let uuid_str: String = row.get(0)?;
                Ok((
                    uuid_str,
                    row.get::<_, i64>(1)?,
                    row.get::<_, i64>(2)?,
                    row.get::<_, String>(3)?,
                    row.get::<_, i32>(4)? == 1,
                ))
            })?;
            
            println!("{:<36} {:<10} {:<10} {:<20} {:<8}",
                style("UUID").bold(),
                style("Quota").bold(),
                style("Used").bold(),
                style("Expires").bold(),
                style("Active").bold()
            );
            
            for user in users.flatten() {
                let quota_gb = user.1 as f64 / 1024.0 / 1024.0 / 1024.0;
                let used_gb = user.2 as f64 / 1024.0 / 1024.0 / 1024.0;
                let active = if user.4 { style("Yes").green() } else { style("No").red() };
                
                println!("{:<36} {:.1} GB    {:.1} GB    {:<20} {}",
                    user.0,
                    quota_gb,
                    used_gb,
                    &user.3[..19], // Trim datetime
                    active
                );
            }
        }
        
        UserCommands::Create { quota, days } => {
            use oncp::UserRegistry;
            
            let pb = ProgressBar::new_spinner();
            pb.set_style(ProgressStyle::default_spinner()
                .template("{spinner:.green} {msg}")
                .unwrap());
            pb.set_message("Creating user...");
            
            let user = User::new(quota, days);
            state.users.create_user(&user)?;
            
            pb.finish_with_message("User created!");
            println!();
            println!("{} User Created", style("✓").green().bold());
            println!();
            println!("  {} {}", style("UUID:").bold(), style(&user.uuid).yellow());
            println!("  {} {} GB", style("Quota:").bold(), quota);
            println!("  {} {} days", style("Valid:").bold(), days);
            println!("  {} {}", style("Expires:").bold(), user.expires_at.format("%Y-%m-%d %H:%M UTC"));
            println!();
            
            // Generate config string for QR/sharing
            let config = serde_json::json!({
                "uuid": user.uuid.to_string(),
                "expires": user.expires_at.to_rfc3339(),
            });
            let config_str = base64_encode(config.to_string());
            
            println!("  {} {}", style("Config:").bold(), style(&config_str).dim());
            println!();
            println!("  Share this config string with the user for client setup.");
        }
        
        UserCommands::Config { id } => {
            use oncp::UserRegistry;
            
            let uuid = uuid::Uuid::parse_str(&id)?;
            match state.users.get_user(&uuid)? {
                Some(user) => {
                    println!("{}", style("User Configuration").green().bold());
                    println!();
                    
                    // Get best servers
                    let servers = state.nodes.best_global(3).await;
                    
                    let config = serde_json::json!({
                        "uuid": user.uuid.to_string(),
                        "servers": servers.iter().map(|s| &s.address).collect::<Vec<_>>(),
                        "expires": user.expires_at.to_rfc3339(),
                    });
                    
                    println!("{}", style("JSON Config:").bold());
                    println!("{}", serde_json::to_string_pretty(&config)?);
                    println!();
                    
                    let encoded = base64_encode(config.to_string());
                    println!("{} {}", style("Base64:").bold(), encoded);
                    
                    // QR code hint
                    println!();
                    println!("{}", style("Tip: Use 'qrencode' to generate QR code from Base64 string").dim());
                }
                None => {
                    println!("{} User not found", style("✗").red().bold());
                }
            }
        }
        
        UserCommands::Delete { id } => {
            let uuid = uuid::Uuid::parse_str(&id)?;
            let conn = state.users.conn.lock().unwrap();
            let deleted = conn.execute("DELETE FROM users WHERE uuid = ?", [id])?;
            
            if deleted > 0 {
                println!("{} User deleted: {}", style("✓").green().bold(), uuid);
            } else {
                println!("{} User not found", style("✗").red().bold());
            }
        }
    }
    
    Ok(())
}

async fn handle_sni_command(state: Arc<AppState>, action: SniCommands) -> Result<()> {
    match action {
        SniCommands::List => {
            let snis = state.sni_manager.get_active_snis().await;
            
            println!("{}", style("Active SNI Domains").green().bold());
            println!("{}", style("─").dim().to_string().repeat(50));
            
            for sni in snis {
                println!("  • {}", sni);
            }
            
            let stats = state.sni_manager.stats().await;
            println!();
            println!("{} total, {} blocked", 
                style(stats.total_domains).bold(),
                style(stats.blocked_domains).red().bold()
            );
        }
        
        SniCommands::Block { domain } => {
            state.sni_manager.block_domain(domain.clone()).await;
            println!("{} Blocked domain: {}", style("✓").green().bold(), style(&domain).red());
            println!("  Emergency update queued for all nodes.");
        }
        
        SniCommands::Add { domain, country } => {
            use oncp::SniUpdate;
            
            let update = SniUpdate {
                remove: vec![],
                add: vec![domain.clone()],
                country_code: Some(country.clone()),
                emergency: false,
            };
            
            state.sni_manager.apply_update(update).await;
            println!("{} Added domain: {} ({})", 
                style("✓").green().bold(), 
                style(&domain).cyan(),
                country
            );
        }
    }
    
    Ok(())
}

async fn show_stats(state: Arc<AppState>) -> Result<()> {
    let stats = state.nodes.network_stats().await;
    let sni_stats = state.sni_manager.stats().await;
    
    println!("{}", style("═══════════════════════════════════════════════").cyan());
    println!("{}", style("       OSTP CDN Network Statistics").cyan().bold());
    println!("{}", style("═══════════════════════════════════════════════").cyan());
    println!();
    
    println!("{}", style("Nodes").green().bold());
    println!("  Total:     {}", stats.total_nodes);
    println!("  Online:    {}", style(stats.online_nodes).green());
    println!("  Offline:   {}", style(stats.total_nodes - stats.online_nodes).red());
    println!();
    
    println!("{}", style("Traffic").green().bold());
    println!("  TX:        {:.2} MB", stats.total_bytes_tx as f64 / 1024.0 / 1024.0);
    println!("  RX:        {:.2} MB", stats.total_bytes_rx as f64 / 1024.0 / 1024.0);
    println!("  Total:     {:.2} MB", (stats.total_bytes_tx + stats.total_bytes_rx) as f64 / 1024.0 / 1024.0);
    println!();
    
    println!("{}", style("Connections").green().bold());
    println!("  Active:    {}", stats.total_connections);
    println!("  Avg Load:  {:.1}%", stats.avg_load * 100.0);
    println!();
    
    println!("{}", style("SNI").green().bold());
    println!("  Domains:   {}", sni_stats.total_domains);
    println!("  Blocked:   {}", style(sni_stats.blocked_domains).red());
    println!("  Countries: {}", sni_stats.countries);
    println!();
    
    Ok(())
}

// Helper for base64 encoding
fn base64_encode(input: impl AsRef<[u8]>) -> String {
    use base64::{Engine as _, engine::general_purpose};
    general_purpose::STANDARD.encode(input)
}
