//! OSTP Server CLI - Stealth VPN Server for Linux
//!
//! Usage:
//!   ostp-server --listen 0.0.0.0:8443 --psk <hex-key>
//!   ostp-server --config /etc/ostp/server.json

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use ostp::{OstpServer, ServerConfig};
use std::net::SocketAddr;
use std::path::PathBuf;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

#[derive(Parser)]
#[command(name = "ostp-server")]
#[command(author = "ospab.team")]
#[command(version)]
#[command(about = "OSTP Stealth VPN Server", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,

    /// Listen address (e.g., 0.0.0.0:8443)
    #[arg(short, long, default_value = "0.0.0.0:8443")]
    listen: SocketAddr,

    /// Pre-shared key in hex format (64 hex chars = 32 bytes)
    #[arg(short, long, env = "OSTP_PSK")]
    psk: Option<String>,

    /// Path to config file (JSON)
    #[arg(short, long)]
    config: Option<PathBuf>,

    /// Log level (trace, debug, info, warn, error)
    #[arg(long, default_value = "info")]
    log_level: String,

    /// Max concurrent connections
    #[arg(long, default_value = "1024")]
    max_connections: usize,
}

#[derive(Subcommand)]
enum Commands {
    /// Generate a new random PSK
    GenKey,
    /// Show server status (placeholder)
    Status,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct ConfigFile {
    listen: String,
    psk: String,
    max_connections: Option<usize>,
    log_level: Option<String>,
    
    // Node enrollment settings
    master_node_url: Option<String>,
    enrollment_token: Option<String>,
    node_name: Option<String>,
    hardware_id: Option<String>,
    region: Option<String>,
    country_code: Option<String>,
}

fn setup_logging(level: &str) {
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new(level));

    tracing_subscriber::registry()
        .with(fmt::layer().with_target(true).with_thread_ids(false))
        .with(filter)
        .init();
}

fn parse_psk(hex_str: &str) -> Result<[u8; 32]> {
    let bytes = hex::decode(hex_str.trim())
        .context("Invalid hex string for PSK")?;
    
    if bytes.len() != 32 {
        anyhow::bail!("PSK must be exactly 32 bytes (64 hex characters), got {}", bytes.len());
    }
    
    let mut psk = [0u8; 32];
    psk.copy_from_slice(&bytes);
    Ok(psk)
}

fn generate_random_psk() -> String {
    let psk: [u8; 32] = rand::random();
    hex::encode(psk)
}

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

    // Handle subcommands
    if let Some(command) = cli.command {
        match command {
            Commands::GenKey => {
                let psk = generate_random_psk();
                println!("Generated PSK (keep this secret!):");
                println!("{}", psk);
                return Ok(());
            }
            Commands::Status => {
                println!("Server status: Not implemented yet");
                return Ok(());
            }
        }
    }

    // Load config from file if specified
    let (listen, psk, log_level, config_path_opt) = if let Some(config_path) = cli.config.clone() {
        let content = std::fs::read_to_string(&config_path)
            .with_context(|| format!("Failed to read config file: {:?}", config_path))?;
        let config: ConfigFile = serde_json::from_str(&content)
            .context("Failed to parse config file")?;
        
        let addr: SocketAddr = config.listen.parse()
            .context("Invalid listen address in config")?;
        let psk = parse_psk(&config.psk)?;
        let level = config.log_level.unwrap_or_else(|| cli.log_level.clone());
        
        (addr, psk, level, Some(config_path))
    } else {
        // Use CLI args
        let psk_str = cli.psk.ok_or_else(|| {
            anyhow::anyhow!("PSK is required. Use --psk <hex> or OSTP_PSK env var, or run 'ostp-server gen-key' to generate one")
        })?;
        let psk = parse_psk(&psk_str)?;
        
        (cli.listen, psk, cli.log_level, None)
    };

    setup_logging(&log_level);

    tracing::info!("╔════════════════════════════════════════════════════════╗");
    tracing::info!("║           OSTP Stealth VPN Server v{}            ║", env!("CARGO_PKG_VERSION"));
    tracing::info!("╚════════════════════════════════════════════════════════╝");
    tracing::info!("");
    tracing::info!("  Listen address: {}", listen);
    tracing::info!("  Max connections: {}", cli.max_connections);
    tracing::info!("  PSK: {}...{}", &hex::encode(&psk[..4]), &hex::encode(&psk[28..]));
    tracing::info!("");

    // Check if node enrollment is configured
    if let Some(config_path) = config_path_opt {
        let content = std::fs::read_to_string(&config_path)?;
        let config: ConfigFile = serde_json::from_str(&content)?;
        
        if let Some(master_url) = config.master_node_url {
            if config.psk == "AUTO" {
                // Node needs to enroll - request PSK from master
                let token = config.enrollment_token.ok_or_else(|| {
                    anyhow::anyhow!("enrollment_token is required when psk is AUTO")
                })?;
                
                tracing::info!("Node not enrolled - requesting to join network...");
                tracing::info!("Using enrollment token: {}...{}", &token[..4], &token[token.len().saturating_sub(4)..]);
                
                let enrollment_result = request_enrollment(
                    &master_url,
                    &token,
                    config.node_name.as_deref().unwrap_or("ostp-node"),
                    &listen.to_string(),
                    config.country_code.as_deref().unwrap_or("US"),
                    config.hardware_id.as_deref().unwrap_or("unknown"),
                    config.region.as_deref().unwrap_or("default"),
                ).await;
                
                match enrollment_result {
                    Ok(node_id) => {
                        tracing::info!("✓ Enrollment request submitted");
                        tracing::info!("  Node ID: {}", node_id);
                        tracing::info!("");
                        tracing::info!("Waiting for master node approval...");
                        tracing::info!("Contact the administrator to approve node: {}", node_id);
                        tracing::info!("Once approved, update config with provided PSK");
                        
                        // Exit - node must wait for approval
                        return Ok(());
                    }
                    Err(e) => {
                        tracing::error!("Failed to submit enrollment: {}", e);
                        tracing::error!("Server will not start until enrolled");
                        return Err(e);
                    }
                }
            }
        }
    }

    let config = ServerConfig::new(listen, psk);
    let server = OstpServer::new(config);

    // ============================================
    // SECURITY CHECK - Detect analysis environment
    // Smart heuristic: production VPS safe
    // ============================================
    #[cfg(not(debug_assertions))]
    {
        if !ostp_guard::init_protection() {
            tracing::error!("Security check failed: environment not suitable for production");
            std::process::exit(1);
        }
        
        // Start background monitor for debugger attachment
        ostp_guard::anti_debug::start_background_monitor();
    }

    tracing::info!("Starting server...");
    server.run().await?;

    Ok(())
}

/// Submit enrollment request to master node
async fn request_enrollment(
    master_url: &str,
    token: &str,
    name: &str,
    address: &str,
    country_code: &str,
    hardware_id: &str,
    region: &str,
) -> Result<String> {
    #[derive(serde::Serialize)]
    struct EnrollmentRequest {
        token: String,
        name: String,
        address: String,
        country_code: String,
        hardware_id: String,
        region: String,
    }

    #[derive(serde::Deserialize)]
    #[allow(dead_code)]
    struct EnrollmentResponse {
        node_id: String,
        state: String,
        message: String,
    }

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()?;

    let request = EnrollmentRequest {
        token: token.to_string(),
        name: name.to_string(),
        address: address.to_string(),
        country_code: country_code.to_string(),
        hardware_id: hardware_id.to_string(),
        region: region.to_string(),
    };

    let url = format!("{}/api/v1/enrollment/request", master_url);
    let response = client
        .post(&url)
        .json(&request)
        .send()
        .await
        .context("Failed to connect to master node")?;

    if !response.status().is_success() {
        let status = response.status();
        let text = response.text().await.unwrap_or_default();
        anyhow::bail!("Enrollment request failed: {} - {}", status, text);
    }

    let enrollment: EnrollmentResponse = response.json().await?;
    Ok(enrollment.node_id)
}
