//! REST API for CDN Control Plane (Master Node)

use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use base64::{Engine as _, engine::general_purpose};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use uuid::Uuid;

use crate::billing::{SqliteRegistry, User, UserRegistry};
use crate::enrollment::{EnrollmentRegistry, EnrollmentRequest, EnrollmentState};
use crate::network::{NetworkConfig, IpamPool};
use crate::node::{NetworkStats, Node, NodeCheckin, NodeRegistry};
use crate::session::SessionManager;
use crate::sni::{SniManager, SniUpdate};
use crate::token::TokenManager;

/// Shared application state
pub struct AppState {
    pub nodes: NodeRegistry,
    pub users: SqliteRegistry,
    pub sessions: SessionManager,
    pub sni_manager: SniManager,
    pub enrollment: EnrollmentRegistry,
    pub token_manager: Arc<TokenManager>,
    pub ipam_pool: Arc<parking_lot::Mutex<IpamPool>>,
}

impl AppState {
    pub fn new(db_path: &str, network_octet: u8) -> anyhow::Result<Self> {
        let network_config = NetworkConfig::new(network_octet)?;
        let ipam_pool = IpamPool::new(network_config);
        
        Ok(Self {
            nodes: NodeRegistry::new(60), // 60 second timeout
            users: SqliteRegistry::new(db_path)?,
            sessions: SessionManager::new(300), // 5 minute heartbeat timeout
            sni_manager: SniManager::new(),
            enrollment: EnrollmentRegistry::new(db_path)?,
            token_manager: Arc::new(TokenManager::new()),
            ipam_pool: Arc::new(parking_lot::Mutex::new(ipam_pool)),
        })
    }
}

/// Create the control plane API router
pub fn create_router(state: Arc<AppState>) -> Router {
    Router::new()
        // Node management
        .route("/api/v1/nodes", get(list_nodes).post(register_node))
        .route("/api/v1/nodes/:id", get(get_node).delete(remove_node))
        .route("/api/v1/nodes/:id/checkin", post(node_checkin))
        .route("/api/v1/nodes/best", get(best_nodes))
        
        // Node enrollment
        .route("/api/v1/enrollment/request", post(submit_enrollment))
        .route("/api/v1/enrollment/pending", get(list_pending_enrollments))
        .route("/api/v1/enrollment/:id/approve", post(approve_enrollment))
        .route("/api/v1/enrollment/:id/reject", post(reject_enrollment))
        
        // User management
        .route("/api/v1/users", get(list_users).post(create_user))
        .route("/api/v1/users/:id", get(get_user).delete(delete_user))
        .route("/api/v1/users/:id/config", get(user_config))
        
        // SNI management
        .route("/api/v1/sni", get(list_sni).post(update_sni))
        .route("/api/v1/sni/emergency", post(emergency_sni_update))
        
        // Statistics
        .route("/api/v1/stats", get(network_stats))
        .route("/api/v1/stats/traffic", get(traffic_stats))
        
        // Health check
        .route("/health", get(health_check))
        
        .layer(TraceLayer::new_for_http())
        .layer(CorsLayer::new().allow_origin(Any).allow_methods(Any))
        .with_state(state)
}

// ============================================================================
// Node Endpoints
// ============================================================================

/// List all nodes
async fn list_nodes(State(state): State<Arc<AppState>>) -> Json<Vec<Node>> {
    Json(state.nodes.list().await)
}

/// Register new node
#[derive(Debug, Deserialize)]
struct RegisterNodeRequest {
    name: String,
    address: String,
    country_code: String,
    max_connections: Option<u32>,
    psk_hash: Option<String>,
}

#[derive(Debug, Serialize)]
struct RegisterNodeResponse {
    node_id: Uuid,
    message: String,
}

async fn register_node(
    State(state): State<Arc<AppState>>,
    Json(req): Json<RegisterNodeRequest>,
) -> impl IntoResponse {
    let mut node = Node::new(&req.name, &req.address, &req.country_code);
    if let Some(max) = req.max_connections {
        node.max_connections = max;
    }
    node.psk_hash = req.psk_hash;
    
    let node_id = state.nodes.register(node).await;
    
    (StatusCode::CREATED, Json(RegisterNodeResponse {
        node_id,
        message: "Node registered successfully".into(),
    }))
}

/// Get single node
async fn get_node(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match state.nodes.get(&id).await {
        Some(node) => (StatusCode::OK, Json(Some(node))),
        None => (StatusCode::NOT_FOUND, Json(None)),
    }
}

/// Remove node
async fn remove_node(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match state.nodes.remove(&id).await {
        Some(_) => StatusCode::NO_CONTENT,
        None => StatusCode::NOT_FOUND,
    }
}

/// Node check-in (heartbeat)
async fn node_checkin(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
    Json(mut checkin): Json<NodeCheckin>,
) -> impl IntoResponse {
    checkin.node_id = id; // Ensure node_id matches path
    
    match state.nodes.checkin(checkin).await {
        Some(node) => (StatusCode::OK, Json(Some(node))),
        None => (StatusCode::NOT_FOUND, Json(None)),
    }
}

/// Get best nodes for client connection (CDN Steering)
#[derive(Debug, Deserialize)]
struct BestNodesQuery {
    country: Option<String>,
    limit: Option<usize>,
}

#[derive(Debug, Serialize)]
struct BestNodesResponse {
    nodes: Vec<Node>,
    steering_info: SteeringInfo,
}

#[derive(Debug, Serialize)]
struct SteeringInfo {
    requested_country: Option<String>,
    matched_country: bool,
    fallback_used: bool,
    total_available: usize,
}

async fn best_nodes(
    State(state): State<Arc<AppState>>,
    Query(query): Query<BestNodesQuery>,
) -> Json<BestNodesResponse> {
    let limit = query.limit.unwrap_or(3);
    
    let (nodes, matched, fallback) = match &query.country {
        Some(country) => {
            let country_nodes = state.nodes.best_for_country(country, limit).await;
            
            if country_nodes.is_empty() {
                // No nodes in requested country - fallback to global
                let global_nodes = state.nodes.best_global(limit).await;
                (global_nodes, false, true)
            } else {
                (country_nodes, true, false)
            }
        }
        None => {
            let global = state.nodes.best_global(limit).await;
            (global, false, false)
        }
    };
    
    let total = state.nodes.list_online().await.len();
    
    Json(BestNodesResponse {
        nodes: nodes.clone(),
        steering_info: SteeringInfo {
            requested_country: query.country.clone(),
            matched_country: matched,
            fallback_used: fallback,
            total_available: total,
        },
    })
}

// ============================================================================
// User Endpoints
// ============================================================================

/// List users (limited info for security)
#[derive(Debug, Serialize)]
struct UserSummary {
    uuid: Uuid,
    active: bool,
    expires_at: String,
    bandwidth_used_gb: f64,
    bandwidth_quota_gb: f64,
}

async fn list_users(State(state): State<Arc<AppState>>) -> impl IntoResponse {
    // Note: In production, this should have pagination and auth
    let conn = state.users.conn.lock().unwrap();
    let mut stmt = conn.prepare(
        "SELECT uuid, bandwidth_quota, bandwidth_used, expires_at, active FROM users LIMIT 100"
    ).unwrap();
    
    let users: Vec<UserSummary> = stmt.query_map([], |row: &rusqlite::Row| {
        let uuid_str: String = row.get(0)?;
        Ok(UserSummary {
            uuid: Uuid::parse_str(&uuid_str).unwrap(),
            bandwidth_quota_gb: row.get::<_, i64>(1)? as f64 / (1024.0 * 1024.0 * 1024.0),
            bandwidth_used_gb: row.get::<_, i64>(2)? as f64 / (1024.0 * 1024.0 * 1024.0),
            expires_at: row.get::<_, String>(3)?,
            active: row.get::<_, i32>(4)? == 1,
        })
    }).unwrap().filter_map(|r: Result<UserSummary, _>| r.ok()).collect();
    
    Json(users)
}

/// Create new user
#[derive(Debug, Deserialize)]
struct CreateUserRequest {
    quota_gb: Option<u64>,
    valid_days: Option<i64>,
}

#[derive(Debug, Serialize)]
struct CreateUserResponse {
    uuid: Uuid,
    config_string: String,
    expires_at: String,
}

async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CreateUserRequest>,
) -> impl IntoResponse {
    let quota = req.quota_gb.unwrap_or(100);
    let days = req.valid_days.unwrap_or(30);
    
    let user = User::new(quota, days);
    
    match state.users.create_user(&user) {
        Ok(()) => {
            // Generate config string (can be used for QR code)
            let config = serde_json::json!({
                "uuid": user.uuid.to_string(),
                "expires": user.expires_at.to_rfc3339(),
            });
            
            (StatusCode::CREATED, Json(CreateUserResponse {
                uuid: user.uuid,
                config_string: general_purpose::STANDARD.encode(config.to_string()),
                expires_at: user.expires_at.to_rfc3339(),
            }))
        }
        Err(e) => {
            tracing::error!("Failed to create user: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(CreateUserResponse {
                uuid: Uuid::nil(),
                config_string: String::new(),
                expires_at: String::new(),
            }))
        }
    }
}

/// Get user details
async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match state.users.get_user(&id) {
        Ok(Some(user)) => (StatusCode::OK, Json(Some(user))),
        Ok(None) => (StatusCode::NOT_FOUND, Json(None)),
        Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json(None)),
    }
}

/// Delete user
async fn delete_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let conn = state.users.conn.lock().unwrap();
    match conn.execute("DELETE FROM users WHERE uuid = ?", [id.to_string()]) {
        Ok(0) => StatusCode::NOT_FOUND,
        Ok(_) => StatusCode::NO_CONTENT,
        Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
    }
}

/// Get user connection config (for client setup)
#[derive(Debug, Serialize)]
struct UserConfig {
    uuid: String,
    servers: Vec<ServerInfo>,
    sni_list: Vec<String>,
    qr_data: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
struct ServerInfo {
    address: String,
    country_code: String,
    load: f32,
}

async fn user_config(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    // Verify user exists and is valid
    match state.users.validate_user(&id) {
        Ok(true) => {}
        _ => return (StatusCode::NOT_FOUND, Json(UserConfig {
            uuid: String::new(),
            servers: vec![],
            sni_list: vec![],
            qr_data: None,
        })),
    }
    
    // Get best available servers
    let nodes = state.nodes.best_global(5).await;
    let servers: Vec<ServerInfo> = nodes.iter().map(|n| ServerInfo {
        address: n.address.clone(),
        country_code: n.country_code.clone(),
        load: n.load_score(),
    }).collect();
    
    // Get current SNI list
    let sni_list = state.sni_manager.get_active_snis().await;
    
    // Generate QR-compatible config
    let qr_config = serde_json::json!({
        "u": id.to_string(),
        "s": servers.first().map(|s| &s.address),
    });
    let qr_data = general_purpose::STANDARD.encode(qr_config.to_string());
    
    (StatusCode::OK, Json(UserConfig {
        uuid: id.to_string(),
        servers,
        sni_list,
        qr_data: Some(qr_data),
    }))
}

// ============================================================================
// SNI Management
// ============================================================================

async fn list_sni(State(state): State<Arc<AppState>>) -> Json<Vec<String>> {
    Json(state.sni_manager.get_active_snis().await)
}

async fn update_sni(
    State(state): State<Arc<AppState>>,
    Json(update): Json<SniUpdate>,
) -> impl IntoResponse {
    state.sni_manager.apply_update(update).await;
    StatusCode::OK
}

/// Emergency SNI update (broadcast to all nodes)
#[derive(Debug, Deserialize)]
struct EmergencySniRequest {
    blocked_domains: Vec<String>,
    replacement_domains: Vec<String>,
    country_code: Option<String>,
}

async fn emergency_sni_update(
    State(state): State<Arc<AppState>>,
    Json(req): Json<EmergencySniRequest>,
) -> impl IntoResponse {
    let update = SniUpdate {
        remove: req.blocked_domains,
        add: req.replacement_domains,
        country_code: req.country_code,
        emergency: true,
    };
    
    state.sni_manager.apply_update(update).await;
    
    // Log for audit
    tracing::warn!("Emergency SNI update applied");
    
    StatusCode::OK
}

// ============================================================================
// Enrollment Endpoints
// ============================================================================

/// Submit node enrollment request
#[derive(Debug, Deserialize)]
struct SubmitEnrollmentRequest {
    token: String,           // OTP enrollment token (required)
    name: String,
    address: String,
    country_code: String,
    hardware_id: String,
    region: String,
}

#[derive(Debug, Serialize)]
struct SubmitEnrollmentResponse {
    node_id: Uuid,
    state: String,
    message: String,
}

async fn submit_enrollment(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SubmitEnrollmentRequest>,
) -> impl IntoResponse {
    // SECURITY: Validate token first - silent drop on failure
    if let Err(e) = state.token_manager.validate(&req.token) {
        tracing::warn!("Enrollment rejected: invalid token ({})", e);
        // Silent drop - return 444 (nginx-style connection close without response)
        // This prevents token enumeration attacks
        return (StatusCode::from_u16(444).unwrap(), Json(SubmitEnrollmentResponse {
            node_id: Uuid::nil(),
            state: "error".into(),
            message: "".into(),
        }));
    }

    let node_id = Uuid::new_v4();
    let node_name = req.name.clone();
    let enrollment_req = EnrollmentRequest {
        token: req.token,
        node_id,
        name: req.name,
        address: req.address,
        country_code: req.country_code,
        hardware_id: req.hardware_id,
        region: req.region,
        requested_at: chrono::Utc::now(),
    };

    match state.enrollment.submit_request(enrollment_req) {
        Ok(()) => {
            tracing::info!("Enrollment request submitted: {} ({})", node_id, node_name);
            (
                StatusCode::CREATED,
                Json(SubmitEnrollmentResponse {
                    node_id,
                    state: "pending".into(),
                    message: "Enrollment request submitted. Awaiting approval.".into(),
                }),
            )
        },
        Err(e) => {
            tracing::error!("Enrollment submission failed: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(SubmitEnrollmentResponse {
                    node_id,
                    state: "error".into(),
                    message: format!("Failed to submit: {}", e),
                }),
            )
        },
    }
}

/// List pending enrollments
async fn list_pending_enrollments(
    State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
    match state.enrollment.list_by_state(EnrollmentState::Pending) {
        Ok(nodes) => (StatusCode::OK, Json(nodes)),
        Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![])),
    }
}

/// Approve enrollment
#[derive(Debug, Serialize)]
struct ApproveEnrollmentResponse {
    node_id: Uuid,
    node_psk: String,
    assigned_ip: String,
    message: String,
}

async fn approve_enrollment(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    // Allocate IP from IPAM pool
    let assigned_ip = match state.ipam_pool.lock().allocate() {
        Ok(ip) => ip,
        Err(e) => {
            return (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(ApproveEnrollmentResponse {
                    node_id: id,
                    node_psk: String::new(),
                    assigned_ip: String::new(),
                    message: format!("Failed to allocate IP: {}", e),
                }),
            );
        }
    };

    match state.enrollment.approve(&id, &assigned_ip.to_string()) {
        Ok(node_psk) => {
            tracing::info!("Node {} approved with IP {}", id, assigned_ip);
            (
                StatusCode::OK,
                Json(ApproveEnrollmentResponse {
                    node_id: id,
                    node_psk,
                    assigned_ip: assigned_ip.to_string(),
                    message: "Node approved. Use provided PSK and IP to connect.".into(),
                }),
            )
        },
        Err(e) => {
            // Release IP on failure
            let _ = state.ipam_pool.lock().release(assigned_ip);
            (
                StatusCode::BAD_REQUEST,
                Json(ApproveEnrollmentResponse {
                    node_id: id,
                    node_psk: String::new(),
                    assigned_ip: String::new(),
                    message: format!("Failed to approve: {}", e),
                }),
            )
        },
    }
}

/// Reject enrollment
async fn reject_enrollment(
    State(state): State<Arc<AppState>>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    match state.enrollment.reject(&id) {
        Ok(()) => StatusCode::NO_CONTENT,
        Err(_) => StatusCode::BAD_REQUEST,
    }
}

// ============================================================================
// Statistics
// ============================================================================

async fn network_stats(State(state): State<Arc<AppState>>) -> Json<NetworkStats> {
    Json(state.nodes.network_stats().await)
}

#[derive(Debug, Serialize)]
struct TrafficStats {
    total_bytes_tx: u64,
    total_bytes_rx: u64,
    total_mb_transferred: f64,
    active_sessions: usize,
}

async fn traffic_stats(State(state): State<Arc<AppState>>) -> Json<TrafficStats> {
    let net_stats = state.nodes.network_stats().await;
    let total_bytes = net_stats.total_bytes_tx + net_stats.total_bytes_rx;
    
    Json(TrafficStats {
        total_bytes_tx: net_stats.total_bytes_tx,
        total_bytes_rx: net_stats.total_bytes_rx,
        total_mb_transferred: total_bytes as f64 / (1024.0 * 1024.0),
        active_sessions: net_stats.total_connections as usize,
    })
}

// ============================================================================
// Health Check
// ============================================================================

#[derive(Debug, Serialize)]
struct HealthStatus {
    status: String,
    version: String,
    nodes_online: usize,
}

async fn health_check(State(state): State<Arc<AppState>>) -> Json<HealthStatus> {
    let stats = state.nodes.network_stats().await;
    
    Json(HealthStatus {
        status: "ok".into(),
        version: env!("CARGO_PKG_VERSION").into(),
        nodes_online: stats.online_nodes,
    })
}

/// Start the control plane API server
pub async fn run_server(state: Arc<AppState>, bind_addr: &str) -> anyhow::Result<()> {
    let app = create_router(state);
    
    let listener = tokio::net::TcpListener::bind(bind_addr).await?;
    tracing::info!("Control Plane API listening on {}", bind_addr);
    
    axum::serve(listener, app).await?;
    
    Ok(())
}
