/**
 * HTTP web interface implementation for wssshd
 *
 * Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include "web.h"
#include "terminal.h"
#include "vnc.h"
#include "rdp.h"
#include "assets.h"
#include "websocket.h"
#include "websocket_protocol.h"
#include "html_pages/index_page.h"
#include "html_pages/login_page.h"
#include "html_pages/terminal_page.h"
#include "html_pages/vnc_page.h"
#include "html_pages/rdp_page.h"
#include "html_pages/users_page.h"

// Embedded web assets are defined in assets.c

static wssshd_state_t *global_state = NULL;
static const wssshd_config_t *global_config = NULL;
static int server_socket = -1;
static volatile int server_running = 0;
static sqlite3 *db = NULL;

// Simple user management
#define MAX_USERS 100
typedef struct {
    int id;
    char username[50];
    char password_hash[100];
    int is_admin;
} web_user_t;

// Session management
#define MAX_SESSIONS 100
typedef struct {
    char session_id[33]; // UUID-like
    char username[50];
    time_t created_at;
    time_t last_access;
} web_session_t;

static web_session_t sessions[MAX_SESSIONS];
static int session_count = 0;
static pthread_mutex_t session_mutex = PTHREAD_MUTEX_INITIALIZER;

// Active terminal sessions
#define MAX_ACTIVE_TERMINALS 100
static terminal_session_t *active_terminals[MAX_ACTIVE_TERMINALS];
static int active_terminals_count = 0;
static pthread_mutex_t terminals_mutex = PTHREAD_MUTEX_INITIALIZER;

// Active VNC sessions
#define MAX_ACTIVE_VNCS 100
static vnc_session_t *active_vncs[MAX_ACTIVE_VNCS];
static int active_vncs_count = 0;
static pthread_mutex_t vncs_mutex = PTHREAD_MUTEX_INITIALIZER;

// Active RDP sessions
#define MAX_ACTIVE_RDPS 100
static rdp_session_t *active_rdps[MAX_ACTIVE_RDPS];
static int active_rdps_count = 0;
static pthread_mutex_t rdps_mutex = PTHREAD_MUTEX_INITIALIZER;

// WebSocket connections for terminals
#define MAX_WEBSOCKET_CONNECTIONS 100
typedef struct {
    ws_connection_t *ws_conn;
    char request_id[37];
    char client_id[256];
    char username[50];
    bool active;
    pthread_t thread;
} websocket_terminal_conn_t;

static websocket_terminal_conn_t websocket_connections[MAX_WEBSOCKET_CONNECTIONS];
static int websocket_connections_count = 0;
static pthread_mutex_t websocket_mutex = PTHREAD_MUTEX_INITIALIZER;

// WebSocket connections for VNC
#define MAX_VNC_WEBSOCKET_CONNECTIONS 100
typedef struct {
    ws_connection_t *ws_conn;
    char request_id[37];
    char client_id[256];
    char username[50];
    bool active;
    pthread_t thread;
} websocket_vnc_conn_t;

static websocket_vnc_conn_t websocket_vnc_connections[MAX_VNC_WEBSOCKET_CONNECTIONS];
static int websocket_vnc_connections_count = 0;
static pthread_mutex_t websocket_vnc_mutex = PTHREAD_MUTEX_INITIALIZER;

// WebSocket connections for RDP
#define MAX_RDP_WEBSOCKET_CONNECTIONS 100
typedef struct {
    ws_connection_t *ws_conn;
    char request_id[37];
    char client_id[256];
    char username[50];
    bool active;
    pthread_t thread;
} websocket_rdp_conn_t;

static websocket_rdp_conn_t websocket_rdp_connections[MAX_RDP_WEBSOCKET_CONNECTIONS];
static int websocket_rdp_connections_count = 0;
static pthread_mutex_t websocket_rdp_mutex = PTHREAD_MUTEX_INITIALIZER;

// JSON response for ended session
static const char ended_json[] = "{\"ended\":true}";

// Base64 encoding for WebSocket text frames is declared in websocket_protocol.h

// WebSocket terminal connection management
static websocket_terminal_conn_t *add_websocket_connection(ws_connection_t *ws_conn, const char *request_id, const char *client_id, const char *username) {
    pthread_mutex_lock(&websocket_mutex);
    if (websocket_connections_count >= MAX_WEBSOCKET_CONNECTIONS) {
        pthread_mutex_unlock(&websocket_mutex);
        return NULL;
    }

    websocket_terminal_conn_t *conn = &websocket_connections[websocket_connections_count++];
    conn->ws_conn = ws_conn;
    if (request_id) {
        strncpy(conn->request_id, request_id, sizeof(conn->request_id) - 1);
        conn->request_id[sizeof(conn->request_id) - 1] = '\0';
    } else {
        conn->request_id[0] = '\0';
    }
    if (client_id) {
        size_t len = strlen(client_id);
        if (len >= sizeof(conn->client_id)) {
            // Client ID too long, truncate safely
            len = sizeof(conn->client_id) - 1;
        }
        memcpy(conn->client_id, client_id, len);
        conn->client_id[len] = '\0';
    } else {
        conn->client_id[0] = '\0';
    }
    if (username) {
        strncpy(conn->username, username, sizeof(conn->username) - 1);
        conn->username[sizeof(conn->username) - 1] = '\0';
    } else {
        conn->username[0] = '\0';
    }
    conn->active = true;

    pthread_mutex_unlock(&websocket_mutex);
    return conn;
}


static void remove_websocket_connection(const char *request_id) {
    pthread_mutex_lock(&websocket_mutex);
    for (int i = 0; i < websocket_connections_count; i++) {
        if (strcmp(websocket_connections[i].request_id, request_id) == 0) {
            websocket_connections[i].active = false;
            // Shift remaining connections
            memmove(&websocket_connections[i], &websocket_connections[i + 1],
                    sizeof(websocket_terminal_conn_t) * (websocket_connections_count - i - 1));
            websocket_connections_count--;
            break;
        }
    }
    pthread_mutex_unlock(&websocket_mutex);
}

// WebSocket VNC connection management
static websocket_vnc_conn_t *add_websocket_vnc_connection(ws_connection_t *ws_conn, const char *request_id, const char *client_id, const char *username) {
    pthread_mutex_lock(&websocket_vnc_mutex);
    if (websocket_vnc_connections_count >= MAX_VNC_WEBSOCKET_CONNECTIONS) {
        pthread_mutex_unlock(&websocket_vnc_mutex);
        return NULL;
    }

    websocket_vnc_conn_t *conn = &websocket_vnc_connections[websocket_vnc_connections_count++];
    conn->ws_conn = ws_conn;
    if (request_id) {
        strncpy(conn->request_id, request_id, sizeof(conn->request_id) - 1);
        conn->request_id[sizeof(conn->request_id) - 1] = '\0';
    } else {
        conn->request_id[0] = '\0';
    }
    if (client_id) {
        size_t len = strlen(client_id);
        if (len >= sizeof(conn->client_id)) {
            len = sizeof(conn->client_id) - 1;
        }
        memcpy(conn->client_id, client_id, len);
        conn->client_id[len] = '\0';
    } else {
        conn->client_id[0] = '\0';
    }
    if (username) {
        strncpy(conn->username, username, sizeof(conn->username) - 1);
        conn->username[sizeof(conn->username) - 1] = '\0';
    } else {
        conn->username[0] = '\0';
    }
    conn->active = true;

    pthread_mutex_unlock(&websocket_vnc_mutex);
    return conn;
}

static void remove_websocket_vnc_connection(const char *request_id) {
    pthread_mutex_lock(&websocket_vnc_mutex);
    for (int i = 0; i < websocket_vnc_connections_count; i++) {
        if (strcmp(websocket_vnc_connections[i].request_id, request_id) == 0) {
            websocket_vnc_connections[i].active = false;
            // Shift remaining connections
            memmove(&websocket_vnc_connections[i], &websocket_vnc_connections[i + 1],
                    sizeof(websocket_vnc_conn_t) * (websocket_vnc_connections_count - i - 1));
            websocket_vnc_connections_count--;
            break;
        }
    }
    pthread_mutex_unlock(&websocket_vnc_mutex);
}

// WebSocket RDP connection management
static websocket_rdp_conn_t *add_websocket_rdp_connection(ws_connection_t *ws_conn, const char *request_id, const char *client_id, const char *username) {
    pthread_mutex_lock(&websocket_rdp_mutex);
    if (websocket_rdp_connections_count >= MAX_RDP_WEBSOCKET_CONNECTIONS) {
        pthread_mutex_unlock(&websocket_rdp_mutex);
        return NULL;
    }

    websocket_rdp_conn_t *conn = &websocket_rdp_connections[websocket_rdp_connections_count++];
    conn->ws_conn = ws_conn;
    if (request_id) {
        strncpy(conn->request_id, request_id, sizeof(conn->request_id) - 1);
        conn->request_id[sizeof(conn->request_id) - 1] = '\0';
    } else {
        conn->request_id[0] = '\0';
    }
    if (client_id) {
        size_t len = strlen(client_id);
        if (len >= sizeof(conn->client_id)) {
            len = sizeof(conn->client_id) - 1;
        }
        memcpy(conn->client_id, client_id, len);
        conn->client_id[len] = '\0';
    } else {
        conn->client_id[0] = '\0';
    }
    if (username) {
        strncpy(conn->username, username, sizeof(conn->username) - 1);
        conn->username[sizeof(conn->username) - 1] = '\0';
    } else {
        conn->username[0] = '\0';
    }
    conn->active = true;

    pthread_mutex_unlock(&websocket_rdp_mutex);
    return conn;
}

static void remove_websocket_rdp_connection(const char *request_id) {
    pthread_mutex_lock(&websocket_rdp_mutex);
    for (int i = 0; i < websocket_rdp_connections_count; i++) {
        if (strcmp(websocket_rdp_connections[i].request_id, request_id) == 0) {
            websocket_rdp_connections[i].active = false;
            // Shift remaining connections
            memmove(&websocket_rdp_connections[i], &websocket_rdp_connections[i + 1],
                    sizeof(websocket_rdp_conn_t) * (websocket_rdp_connections_count - i - 1));
            websocket_rdp_connections_count--;
            break;
        }
    }
    pthread_mutex_unlock(&websocket_rdp_mutex);
}

// WebSocket terminal handler thread
static void *websocket_terminal_handler(void *arg) {
    websocket_terminal_conn_t *ws_conn = (websocket_terminal_conn_t *)arg;

    printf("WebSocket terminal handler started for client: %s\n", ws_conn->client_id);

    // Wait for terminal session to be established (up to 30 seconds)
    terminal_session_t *session = NULL;
    int wait_count = 0;
    const int max_wait = 300; // 30 seconds at 100ms intervals

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN && wait_count < max_wait) {
        pthread_mutex_lock(&terminals_mutex);
        for (int i = 0; i < active_terminals_count; i++) {
            if (strcmp(active_terminals[i]->request_id, ws_conn->request_id) == 0 && strlen(ws_conn->request_id) > 0) {
                session = active_terminals[i];
                break;
            }
        }
        pthread_mutex_unlock(&terminals_mutex);

        if (session) {
            printf("WebSocket terminal handler found session for request_id: %s\n", ws_conn->request_id);
            break;
        }

        usleep(100000); // 100ms
        wait_count++;
    }

    if (!session) {
        printf("WebSocket terminal handler timed out waiting for session for client: %s\n", ws_conn->client_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Terminal session not established\"}", 45);
        remove_websocket_connection(ws_conn->request_id);
        return NULL;
    }

    printf("WebSocket terminal handler running for request_id: %s\n", ws_conn->request_id);

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN) {
        // Check if terminal session is still active
        if (!terminal_is_running(session)) {
            // Terminal session ended
            printf("WebSocket terminal session ended for request_id: %s\n", ws_conn->request_id);
            ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, ended_json, sizeof(ended_json) - 1);
            break;
        }

        // Send any available output immediately
        size_t output_len = 0;
        char *output = terminal_get_output(session, &output_len);
        if (output && output_len > 0) {
            printf("WebSocket sending %zu bytes of output for request_id: %s\n", output_len, ws_conn->request_id);
            ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc);
            free(output);
        }

        // Check for input from WebSocket (non-blocking)
        uint8_t opcode = 0;
        void *data = NULL;
        size_t len = 0;

        if (ws_receive_frame(ws_conn->ws_conn, &opcode, &data, &len)) {
            if (opcode == WS_OPCODE_TEXT && len > 0) {
                // Input data
                printf("WebSocket received %zu bytes of input for request_id: %s\n", len, ws_conn->request_id);
                terminal_send_data(session, (const char *)data);
            } else if (opcode == WS_OPCODE_CLOSE) {
                // Connection closed
                printf("WebSocket connection closed for request_id: %s\n", ws_conn->request_id);
                break;
            }
            if (data) free(data);
        } else {
            // No data available, small delay
            usleep(10000); // 10ms
        }
    }

    printf("WebSocket terminal handler ended for request_id: %s\n", ws_conn->request_id);
    remove_websocket_connection(ws_conn->request_id);
    return NULL;
}

// WebSocket VNC handler thread
static void *websocket_vnc_handler(void *arg) {
    websocket_vnc_conn_t *ws_conn = (websocket_vnc_conn_t *)arg;

    printf("WebSocket VNC handler started for client: %s\n", ws_conn->client_id);

    // Launch wsssht for VNC immediately when WebSocket opens
    if (global_config && global_config->debug) {
        printf("[DEBUG] Launching wsssht for VNC client %s\n", ws_conn->client_id);
    }

    vnc_session_t *session = vnc_create_session(global_config, ws_conn->client_id, ws_conn->username, global_config->debug_vnc);
    if (!session) {
        printf("Failed to create VNC session for client: %s\n", ws_conn->client_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Failed to launch VNC session\"}", 40);
        remove_websocket_vnc_connection(ws_conn->request_id);
        return NULL;
    }

    // Update the WebSocket connection with the request_id
    size_t len = strlen(session->request_id);
    if (len >= sizeof(ws_conn->request_id)) {
        len = sizeof(ws_conn->request_id) - 1;
    }
    memcpy(ws_conn->request_id, session->request_id, len);
    ws_conn->request_id[len] = '\0';

    // Add session to active list
    pthread_mutex_lock(&vncs_mutex);
    if (active_vncs_count < MAX_ACTIVE_VNCS) {
        active_vncs[active_vncs_count++] = session;
        printf("WebSocket VNC handler running for request_id: %s\n", ws_conn->request_id);
    } else {
        vnc_free_session(session);
        printf("Too many active VNC sessions, connection rejected for client: %s\n", ws_conn->client_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Too many active VNC sessions\"}", 38);
        remove_websocket_vnc_connection(ws_conn->request_id);
        pthread_mutex_unlock(&vncs_mutex);
        return NULL;
    }
    pthread_mutex_unlock(&vncs_mutex);

    // Wait for VNC connection to be established and RFB handshake to complete
    sleep(1);
    // Add a delay to allow the RFB client to complete the handshake before sending VNC data
    sleep(2);

    // Wait for the VNC connection to establish (up to 20 seconds)
    int wait_count = 0;
    const int max_wait = 400; // 20 seconds at 50ms intervals
    bool connection_ready = false;

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN && wait_count < max_wait && !connection_ready) {
        // Check if VNC session is still active
        if (!vnc_is_running(session)) {
            printf("WebSocket VNC session ended during connection establishment for request_id: %s\n", ws_conn->request_id);
            ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"VNC session failed to start\"}", 39);
            break;
        }

        // Check for initial output from VNC server
        size_t output_len = 0;
        char *output = vnc_get_output(session, &output_len);
        if (output && output_len > 0) {
            printf("WebSocket VNC connection established, received %zu bytes of initial data for request_id: %s\n", output_len, ws_conn->request_id);
            if (global_config->debug_vnc) {
                printf("VNC initial data (first 20 bytes): ");
                for (size_t i = 0; i < output_len && i < 20; i++) {
                    printf("%02x ", (unsigned char)output[i]);
                }
                printf("\n");
            }
            // Send initial data to WebSocket
            if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
                free(output);
                remove_websocket_vnc_connection(ws_conn->request_id);
                return NULL;
            }
            free(output);
            connection_ready = true;
            break;
        } else if (output) {
            free(output);
        }

        usleep(50000); // 50ms delay
        wait_count++;
    }

    if (!connection_ready) {
        printf("WebSocket VNC connection timed out waiting for establishment for request_id: %s\n", ws_conn->request_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"VNC connection timeout\"}", 33);
        remove_websocket_vnc_connection(ws_conn->request_id);
        return NULL;
    }

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN) {
        // Check if VNC session is still active
        if (!vnc_is_running(session)) {
            // VNC session ended
            printf("WebSocket VNC session ended for request_id: %s\n", ws_conn->request_id);
            ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, ended_json, sizeof(ended_json) - 1);
            break;
        }

        // Send any available output immediately
        if (ws_conn->ws_conn->state == WS_STATE_OPEN) {
            size_t output_len = 0;
            char *output = vnc_get_output(session, &output_len);
            if (output && output_len > 0) {
                printf("WebSocket sending %zu bytes of VNC output for request_id: %s\n", output_len, ws_conn->request_id);
                if (global_config->debug_vnc) {
                    printf("VNC output data (first 20 bytes): ");
                    for (size_t i = 0; i < output_len && i < 20; i++) {
                        printf("%02x ", (unsigned char)output[i]);
                    }
                    printf("\n");
                }

                // Send VNC data as a single WebSocket frame - don't chunk it
                // VNC is a streaming protocol and chunking breaks the decoder
                if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
                    printf("Failed to send VNC data frame, disconnecting...\n");
                    free(output);
                    remove_websocket_vnc_connection(ws_conn->request_id);
                    return NULL;
                }
                free(output);
            }
        } else {
            break;
        }

        // Check for input from WebSocket (non-blocking)
        uint8_t opcode = 0;
        void *data = NULL;
        size_t len = 0;

        if (ws_receive_frame(ws_conn->ws_conn, &opcode, &data, &len)) {
            if (opcode == WS_OPCODE_BINARY && len > 0) {
                // VNC input data - this is raw binary data from noVNC
                printf("WebSocket received %zu bytes of VNC input for request_id: %s\n", len, ws_conn->request_id);
                if (global_config->debug_vnc) {
                    printf("VNC input data (first 20 bytes): ");
                    for (size_t i = 0; i < len && i < 20; i++) {
                        printf("%02x ", (unsigned char)((const char *)data)[i]);
                    }
                    printf("\n");
                }
                vnc_send_data(session, (const char *)data, len);
            } else if (opcode == WS_OPCODE_CLOSE) {
                // Connection closed
                printf("WebSocket VNC connection closed for request_id: %s\n", ws_conn->request_id);
                break;
            }
            if (data) free(data);
        } else {
            // No data available, small delay
            usleep(10000); // 10ms
        }
    }

    // Clean up the VNC session
    pthread_mutex_lock(&vncs_mutex);
    for (int i = 0; i < active_vncs_count; i++) {
        if (strcmp(active_vncs[i]->request_id, ws_conn->request_id) == 0) {
            vnc_disconnect(active_vncs[i]);
            vnc_free_session(active_vncs[i]);
            memmove(&active_vncs[i], &active_vncs[i + 1], sizeof(vnc_session_t *) * (active_vncs_count - i - 1));
            active_vncs_count--;
            break;
        }
    }
    pthread_mutex_unlock(&vncs_mutex);

    printf("WebSocket VNC handler ended for request_id: %s\n", ws_conn->request_id);
    remove_websocket_vnc_connection(ws_conn->request_id);
    return NULL;
}

// WebSocket RDP handler thread
static void *websocket_rdp_handler(void *arg) {
    websocket_rdp_conn_t *ws_conn = (websocket_rdp_conn_t *)arg;

    printf("WebSocket RDP handler started for client: %s\n", ws_conn->client_id);

    // Launch wsssht for RDP immediately when WebSocket opens
    if (global_config && global_config->debug_rdp) {
        printf("[DEBUG] Launching wsssht for RDP client %s\n", ws_conn->client_id);
    }

    rdp_session_t *session = rdp_create_session(global_config, ws_conn->client_id, ws_conn->username, global_config->debug_rdp);
    if (!session) {
        printf("Failed to create RDP session for client: %s\n", ws_conn->client_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Failed to launch RDP session\"}", 40);
        remove_websocket_rdp_connection(ws_conn->request_id);
        return NULL;
    }

    // Update the WebSocket connection with the request_id
    size_t len = strlen(session->request_id);
    if (len >= sizeof(ws_conn->request_id)) {
        len = sizeof(ws_conn->request_id) - 1;
    }
    memcpy(ws_conn->request_id, session->request_id, len);
    ws_conn->request_id[len] = '\0';

    // Add session to active list
    pthread_mutex_lock(&rdps_mutex);
    if (active_rdps_count < MAX_ACTIVE_RDPS) {
        active_rdps[active_rdps_count++] = session;
        printf("WebSocket RDP handler running for request_id: %s\n", ws_conn->request_id);
    } else {
        rdp_free_session(session);
        printf("Too many active RDP sessions, connection rejected for client: %s\n", ws_conn->client_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Too many active RDP sessions\"}", 38);
        remove_websocket_rdp_connection(ws_conn->request_id);
        pthread_mutex_unlock(&rdps_mutex);
        return NULL;
    }
    pthread_mutex_unlock(&rdps_mutex);

    // Wait for RDP connection to be established and RDP handshake to complete
    sleep(1);
    // Add a delay to allow the RDP client to complete the handshake before sending RDP data
    sleep(2);

    // Wait for the RDP connection to establish (up to 20 seconds)
    int wait_count = 0;
    const int max_wait = 400; // 20 seconds at 50ms intervals
    bool connection_ready = false;

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN && wait_count < max_wait && !connection_ready) {
        // Check if RDP session is still active
        if (!rdp_is_running(session)) {
            printf("WebSocket RDP session ended during connection establishment for request_id: %s\n", ws_conn->request_id);
            ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"RDP session failed to start\"}", 39);
            break;
        }

        // Check for initial output from RDP server
        size_t output_len = 0;
        char *output = rdp_get_output(session, &output_len);
        if (output && output_len > 0) {
            printf("WebSocket RDP connection established, received %zu bytes of initial data for request_id: %s\n", output_len, ws_conn->request_id);
            if (global_config->debug_rdp) {
                printf("RDP initial data (first 20 bytes): ");
                for (size_t i = 0; i < output_len && i < 20; i++) {
                    printf("%02x ", (unsigned char)output[i]);
                }
                printf("\n");
            }
            // Send initial data to WebSocket
            if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_rdp)) {
                free(output);
                remove_websocket_rdp_connection(ws_conn->request_id);
                return NULL;
            }
            free(output);
            connection_ready = true;
            break;
        } else if (output) {
            free(output);
        }

        usleep(50000); // 50ms delay
        wait_count++;
    }

    if (!connection_ready) {
        printf("WebSocket RDP connection timed out waiting for establishment for request_id: %s\n", ws_conn->request_id);
        ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"RDP connection timeout\"}", 33);
        remove_websocket_rdp_connection(ws_conn->request_id);
        return NULL;
    }

    while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN) {
        // Check if RDP session is still active
        if (!rdp_is_running(session)) {
            // RDP session ended
            printf("WebSocket RDP session ended for request_id: %s\n", ws_conn->request_id);
            ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, ended_json, sizeof(ended_json) - 1);
            break;
        }

        // Send any available output immediately
        size_t output_len = 0;
        char *output = rdp_get_output(session, &output_len);
        if (output && output_len > 0) {
            printf("WebSocket sending %zu bytes of RDP output for request_id: %s\n", output_len, ws_conn->request_id);
            if (global_config->debug_rdp) {
                printf("RDP output data (first 20 bytes): ");
                for (size_t i = 0; i < output_len && i < 20; i++) {
                    printf("%02x ", (unsigned char)output[i]);
                }
                printf("\n");
            }

            // Send RDP data as binary frames (not base64-encoded text)
            if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_rdp)) {
                printf("Failed to send RDP data frame, disconnecting...\n");
                free(output);
                remove_websocket_rdp_connection(ws_conn->request_id);
                return NULL;
            }
            free(output);
        }

        // Check for input from WebSocket (non-blocking)
        uint8_t opcode = 0;
        void *data = NULL;
        size_t len = 0;

        if (ws_receive_frame(ws_conn->ws_conn, &opcode, &data, &len)) {
            if (opcode == WS_OPCODE_BINARY && len > 0) {
                // RDP input data - this is raw binary data from mstsc.js
                printf("WebSocket received %zu bytes of RDP input for request_id: %s\n", len, ws_conn->request_id);
                if (global_config->debug_rdp) {
                    printf("RDP input data (first 20 bytes): ");
                    for (size_t i = 0; i < len && i < 20; i++) {
                        printf("%02x ", (unsigned char)((const char *)data)[i]);
                    }
                    printf("\n");
                }
                rdp_send_data(session, (const char *)data, len);
            } else if (opcode == WS_OPCODE_CLOSE) {
                // Connection closed
                printf("WebSocket RDP connection closed for request_id: %s\n", ws_conn->request_id);
                break;
            }
            if (data) free(data);
        } else {
            // No data available, small delay
            usleep(10000); // 10ms
        }
    }

    printf("WebSocket RDP handler ended for request_id: %s\n", ws_conn->request_id);
    remove_websocket_rdp_connection(ws_conn->request_id);
    return NULL;
}

// Simple hash function for passwords (not secure, but for demo)
static void simple_hash(const char *input, char *output, size_t output_size) {
    unsigned long hash = 5381;
    int c;
    while ((c = *input++)) {
        hash = ((hash << 5) + hash) + c;
    }
    snprintf(output, output_size, "%lx", hash);
}

// Database initialization
static int init_database(const wssshd_config_t *config) {
    char *config_dir = NULL;
    char db_path[1024];
    char *err_msg = NULL;
    int rc;

    if (config->debug_database) {
        printf("[DB-DEBUG] Initializing database...\n");
    }

    // Determine config directory based on user privileges
    char user_config_dir[1024];
    char system_config_dir[1024] = "/etc/wssshd";

    // Check if running as root
    if (getuid() == 0) {
        // Running as root - prefer system directory
        strcpy(db_path, system_config_dir);
        if (config->debug_database) {
            printf("[DB-DEBUG] Running as root, using system config directory: %s\n", db_path);
        }
    } else {
        // Running as regular user - use user directory
        config_dir = getenv("HOME");
        if (!config_dir) {
            fprintf(stderr, "Failed to get HOME environment variable\n");
            return -1;
        }
        snprintf(user_config_dir, sizeof(user_config_dir), "%s/.config/wssshd", config_dir);
        strcpy(db_path, user_config_dir);
        if (config->debug_database) {
            printf("[DB-DEBUG] Running as user, using config directory: %s\n", db_path);
        }
    }

    // Check if preferred directory exists
    if (access(db_path, F_OK) != 0) {
        // Preferred directory doesn't exist
        if (getuid() == 0) {
            // Running as root, check if user directory exists
            config_dir = getenv("HOME");
            if (config_dir) {
                snprintf(user_config_dir, sizeof(user_config_dir), "%s/.config/wssshd", config_dir);
                if (access(user_config_dir, F_OK) == 0) {
                    // User directory exists, use it
                    strcpy(db_path, user_config_dir);
                    if (config->debug_database) {
                        printf("[DB-DEBUG] User config directory exists, using: %s\n", db_path);
                    }
                }
            }
        } else {
            // Running as user, check if system directory exists
            if (access(system_config_dir, F_OK) == 0) {
                // System directory exists, use it
                strcpy(db_path, system_config_dir);
                if (config->debug_database) {
                    printf("[DB-DEBUG] System config directory exists, using: %s\n", db_path);
                }
            }
        }
    }

    if (config->debug_database) {
        printf("[DB-DEBUG] Final config directory: %s\n", db_path);
    }

    if (mkdir(db_path, 0755) == -1 && errno != EEXIST) {
        perror("Failed to create config directory");
        return -1;
    }

    char db_file_path[1024];
    snprintf(db_file_path, sizeof(db_file_path), "%s/users.db", db_path);
    strcpy(db_path, db_file_path);

    if (config->debug_database) {
        printf("[DB-DEBUG] Database path: %s\n", db_path);
        printf("[DB-DEBUG] Opening database...\n");
    }

    rc = sqlite3_open(db_path, &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    if (config->debug_database) {
        printf("[DB-DEBUG] Database opened successfully\n");
    }

    // Create users table
    const char *sql = "CREATE TABLE IF NOT EXISTS users ("
                      "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                      "username TEXT UNIQUE NOT NULL,"
                      "password_hash TEXT NOT NULL,"
                      "is_admin INTEGER DEFAULT 0);";

    if (config->debug_database) {
        printf("[DB-DEBUG] Creating users table...\n");
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    rc = sqlite3_exec(db, sql, NULL, NULL, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL error: %s\n", err_msg);
        sqlite3_free(err_msg);
        sqlite3_close(db);
        return -1;
    }

    if (config->debug_database) {
        printf("[DB-DEBUG] Users table created successfully\n");
    }

    // Check if admin user exists, create if not
    sql = "SELECT COUNT(*) FROM users WHERE is_admin = 1;";

    if (config->debug_database) {
        printf("[DB-DEBUG] Checking if any admin user exists...\n");
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return -1;
    }

    int count = 0;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        count = sqlite3_column_int(stmt, 0);
    }
    sqlite3_finalize(stmt);

    if (config->debug_database) {
        printf("[DB-DEBUG] Admin user count: %d\n", count);
    }

    if (count == 0) {
        // Create default admin user
        char hashed[100];
        simple_hash("admin123", hashed, sizeof(hashed));

        if (config->debug_database) {
            printf("[DB-DEBUG] Creating default admin user...\n");
            printf("[DB-DEBUG] Password hash: %s\n", hashed);
        }

        sql = "INSERT INTO users (username, password_hash, is_admin) VALUES ('admin', ?, 1);";
        rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
        if (rc != SQLITE_OK) {
            fprintf(stderr, "Failed to prepare insert statement: %s\n", sqlite3_errmsg(db));
            sqlite3_close(db);
            return -1;
        }

        sqlite3_bind_text(stmt, 1, hashed, -1, SQLITE_STATIC);
        rc = sqlite3_step(stmt);
        if (rc != SQLITE_DONE) {
            fprintf(stderr, "Failed to insert admin user: %s\n", sqlite3_errmsg(db));
            sqlite3_finalize(stmt);
            sqlite3_close(db);
            return -1;
        }
        sqlite3_finalize(stmt);

        if (config->debug_database) {
            printf("[DB-DEBUG] Default admin user created successfully\n");
        }

        printf("Created default admin user (username: admin, password: admin123)\n");
    } else {
        if (config->debug_database) {
            printf("[DB-DEBUG] Admin user already exists, skipping creation\n");
        }

        // Check if default admin with default password exists (security warning)
        char default_hashed[100];
        simple_hash("admin123", default_hashed, sizeof(default_hashed));

        sql = "SELECT COUNT(*) FROM users WHERE username = 'admin' AND password_hash = ?;";
        rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
        if (rc == SQLITE_OK) {
            sqlite3_bind_text(stmt, 1, default_hashed, -1, SQLITE_STATIC);
            if (sqlite3_step(stmt) == SQLITE_ROW) {
                int default_admin_count = sqlite3_column_int(stmt, 0);
                if (default_admin_count > 0) {
                    printf("\n");
                    printf("###############################################################################\n");
                    printf("#                               SECURITY WARNING                              #\n");
                    printf("###############################################################################\n");
                    printf("#                                                                             #\n");
                    printf("#  WARNING: Default admin credentials detected!                               #\n");
                    printf("#                                                                             #\n");
                    printf("#  The system has detected that the default admin user 'admin' with the       #\n");
                    printf("#  default password 'admin123' is still active.                               #\n");
                    printf("#                                                                             #\n");
                    printf("#  This is a SECURITY RISK! Please change the default password immediately.   #\n");
                    printf("#                                                                             #\n");
                    printf("#  To change the password:                                                    #\n");
                    printf("#  1. Start the web interface                                                 #\n");
                    printf("#  2. Log in with username 'admin' and password 'admin123'                    #\n");
                    printf("#  3. Go to User Management and change the password                           #\n");
                    printf("#                                                                             #\n");
                    printf("###############################################################################\n");
                    printf("\n");
                }
            }
            sqlite3_finalize(stmt);
        }
    }

    return 0;
}

// Initialize database and default users
static void init_users(const wssshd_config_t *config) {
    if (init_database(config) != 0) {
        fprintf(stderr, "Failed to initialize database\n");
        exit(1);
    }
}

// Generate simple session ID
static void generate_session_id(char *session_id, size_t size) {
    static unsigned long counter = 0;
    snprintf(session_id, size, "%lx%lx", (unsigned long)time(NULL), counter++);
}

// Find user by username
static web_user_t *find_user(const char *username) {
    static web_user_t user;
    sqlite3_stmt *stmt;
    const char *sql = "SELECT id, username, password_hash, is_admin FROM users WHERE username = ?;";

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Finding user: %s\n", username);
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare find_user statement: %s\n", sqlite3_errmsg(db));
        return NULL;
    }

    sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
    rc = sqlite3_step(stmt);

    if (rc == SQLITE_ROW) {
        user.id = sqlite3_column_int(stmt, 0);
        const char *uname = (const char *)sqlite3_column_text(stmt, 1);
        const char *phash = (const char *)sqlite3_column_text(stmt, 2);
        int is_admin = sqlite3_column_int(stmt, 3);

        strncpy(user.username, uname, sizeof(user.username) - 1);
        strncpy(user.password_hash, phash, sizeof(user.password_hash) - 1);
        user.is_admin = is_admin;

        if (global_config && global_config->debug_database) {
            printf("[DB-DEBUG] User found: id=%d, username=%s, is_admin=%d\n", user.id, user.username, user.is_admin);
        }

        sqlite3_finalize(stmt);
        return &user;
    }

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] User not found: %s\n", username);
    }

    sqlite3_finalize(stmt);
    return NULL;
}

// Add a new user
static int db_add_user(const char *username, const char *password_hash, int is_admin) {
    sqlite3_stmt *stmt;
    const char *sql = "INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?);";

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Adding user: username=%s, is_admin=%d\n", username, is_admin);
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare add_user statement: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, password_hash, -1, SQLITE_STATIC);
    sqlite3_bind_int(stmt, 3, is_admin);

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (rc != SQLITE_DONE) {
        fprintf(stderr, "Failed to add user: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] User added successfully\n");
    }

    return 0;
}

// Update user
static int db_update_user(int user_id, const char *username, int is_admin, const char *password_hash) {
    sqlite3_stmt *stmt;
    const char *sql;

    if (password_hash) {
        sql = "UPDATE users SET username = ?, is_admin = ?, password_hash = ? WHERE id = ?;";
    } else {
        sql = "UPDATE users SET username = ?, is_admin = ? WHERE id = ?;";
    }

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Updating user: id=%d, username=%s, is_admin=%d, password_change=%s\n",
               user_id, username, is_admin, password_hash ? "yes" : "no");
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare update_user statement: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
    sqlite3_bind_int(stmt, 2, is_admin);
    if (password_hash) {
        sqlite3_bind_text(stmt, 3, password_hash, -1, SQLITE_STATIC);
        sqlite3_bind_int(stmt, 4, user_id);
    } else {
        sqlite3_bind_int(stmt, 3, user_id);
    }

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (rc != SQLITE_DONE) {
        fprintf(stderr, "Failed to update user: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] User updated successfully\n");
    }

    return 0;
}

// Delete user
static int db_delete_user(int user_id) {
    sqlite3_stmt *stmt;
    const char *sql = "DELETE FROM users WHERE id = ?;";

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Deleting user: id=%d\n", user_id);
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare delete_user statement: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    sqlite3_bind_int(stmt, 1, user_id);

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (rc != SQLITE_DONE) {
        fprintf(stderr, "Failed to delete user: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] User deleted successfully\n");
    }

    return 0;
}

// Get all users (for users page)
static int db_get_all_users(web_user_t *users_array, int max_users) {
    sqlite3_stmt *stmt;
    const char *sql = "SELECT id, username, password_hash, is_admin FROM users ORDER BY username;";
    int count = 0;

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Getting all users (max: %d)\n", max_users);
        printf("[DB-DEBUG] SQL: %s\n", sql);
    }

    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to prepare get_all_users statement: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    while (sqlite3_step(stmt) == SQLITE_ROW && count < max_users) {
        users_array[count].id = sqlite3_column_int(stmt, 0);
        const char *uname = (const char *)sqlite3_column_text(stmt, 1);
        const char *phash = (const char *)sqlite3_column_text(stmt, 2);
        int is_admin = sqlite3_column_int(stmt, 3);

        users_array[count].id = sqlite3_column_int(stmt, 0);
        strncpy(users_array[count].username, uname, sizeof(users_array[count].username) - 1);
        strncpy(users_array[count].password_hash, phash, sizeof(users_array[count].password_hash) - 1);
        users_array[count].is_admin = is_admin;

        if (global_config && global_config->debug_database) {
            printf("[DB-DEBUG] Found user: id=%d, username=%s, is_admin=%d\n",
                   users_array[count].id, users_array[count].username, users_array[count].is_admin);
        }

        count++;
    }

    sqlite3_finalize(stmt);

    if (global_config && global_config->debug_database) {
        printf("[DB-DEBUG] Total users retrieved: %d\n", count);
    }

    return count;
}

// Create new session
static const char *create_session(const char *username) {
    pthread_mutex_lock(&session_mutex);
    if (session_count >= MAX_SESSIONS) {
        // Remove oldest session
        memmove(&sessions[0], &sessions[1], sizeof(web_session_t) * (MAX_SESSIONS - 1));
        session_count--;
    }

    generate_session_id(sessions[session_count].session_id, sizeof(sessions[session_count].session_id));
    strcpy(sessions[session_count].username, username);
    sessions[session_count].created_at = time(NULL);
    sessions[session_count].last_access = time(NULL);
    session_count++;
    pthread_mutex_unlock(&session_mutex);

    return sessions[session_count - 1].session_id;
}

// Validate session
static const char *validate_session(const char *session_id) {
    pthread_mutex_lock(&session_mutex);
    for (int i = 0; i < session_count; i++) {
        if (strcmp(sessions[i].session_id, session_id) == 0) {
            // Check if session is not expired (24 hours)
            if (time(NULL) - sessions[i].created_at < 86400) {
                sessions[i].last_access = time(NULL);
                pthread_mutex_unlock(&session_mutex);
                return sessions[i].username;
            } else {
                // Remove expired session
                memmove(&sessions[i], &sessions[i + 1], sizeof(web_session_t) * (session_count - i - 1));
                session_count--;
                break;
            }
        }
    }
    pthread_mutex_unlock(&session_mutex);
    return NULL;
}

// Parse HTTP request
typedef struct {
    char method[10];
    char path[1024];
    char query[1024];
    char headers[4096];
    char body[4096];
    int content_length;
} http_request_t;

static int parse_http_request(int client_fd, http_request_t *req) {
    char buffer[8192];
    ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_read <= 0) return -1;

    buffer[bytes_read] = '\0';

    if (global_config && global_config->debug_web) {
        printf("[WEB-DEBUG] Raw buffer (%zd bytes): '", bytes_read);
        for (ssize_t i = 0; i < bytes_read; i++) {
            if (buffer[i] == '\r') printf("\\r");
            else if (buffer[i] == '\n') printf("\\n");
            else if (buffer[i] >= 32 && buffer[i] <= 126) printf("%c", buffer[i]);
            else printf("\\x%02x", (unsigned char)buffer[i]);
        }
        printf("'\n");
    }

    // Parse request line
    char *line = strtok(buffer, "\r\n");
    if (!line) return -1;

    sscanf(line, "%9s %1023s", req->method, req->path);

    // Parse query string
    char *query = strchr(req->path, '?');
    if (query) {
        *query = '\0';
        strcpy(req->query, query + 1);
    } else {
        req->query[0] = '\0';
    }

    // Parse headers and body
    // First, copy all headers
    char *header_start = line + strlen(line) + 2;
    strcpy(req->headers, header_start);

    // Look for Content-Length header
    int content_length = 0;
    char *cl_header = strstr(req->headers, "Content-Length:");
    if (cl_header) {
        cl_header += 15; // Skip "Content-Length:"
        while (*cl_header == ' ' || *cl_header == '\t') cl_header++;
        content_length = atoi(cl_header);
    }

    if (global_config && global_config->debug_web && !strstr(req->path, "/xterm/data")) {
        printf("[WEB-DEBUG] Content-Length from header: %d\n", content_length);
    }

    // Find body start - look for \r\n\r\n or calculate from Content-Length
    char *body_separator = strstr(buffer, "\r\n\r\n");
    char *body_start = NULL;
    size_t body_length = 0;

    if (global_config && global_config->debug_web && !strstr(req->path, "/xterm/data")) {
        printf("[WEB-DEBUG] Looking for body separator...\n");
    }

    if (body_separator) {
        body_start = body_separator + 4;
        body_length = bytes_read - (body_start - buffer);
        if (global_config && global_config->debug_web) {
            printf("[WEB-DEBUG] Found \\r\\n\\r\\n separator at offset %ld, body_start at %ld, body_length: %zu\n",
                   body_separator - buffer, body_start - buffer, body_length);
        }
    } else if (content_length > 0) {
        // Calculate body position: total bytes - content_length
        body_start = buffer + bytes_read - content_length;
        body_length = content_length;
        if (global_config && global_config->debug_web) {
            printf("[WEB-DEBUG] Calculated body start from Content-Length, body_start at %ld, body_length: %zu\n",
                   body_start - buffer, body_length);
        }
    }

    // Copy body if found
    if (body_start && body_length > 0) {
        if (body_length < sizeof(req->body)) {
            memcpy(req->body, body_start, body_length);
            req->body[body_length] = '\0';
            req->content_length = body_length;
        } else {
            memcpy(req->body, body_start, sizeof(req->body) - 1);
            req->body[sizeof(req->body) - 1] = '\0';
            req->content_length = sizeof(req->body) - 1;
        }
    } else {
        req->body[0] = '\0';
        req->content_length = 0;
    }

    // Clean up headers - remove body if it's appended
    char *headers_end = strstr(req->headers, "\r\n\r\n");
    if (headers_end) {
        *headers_end = '\0';
    } else {
        // Try to find where body starts in headers and truncate
        char *body_in_headers = strstr(req->headers, "\r\nusername=");
        if (body_in_headers) {
            *body_in_headers = '\0';
        }
    }

    return 0;
}

// URL decode
static void url_decode(char *str) {
    char *src = str;
    char *dst = str;
    while (*src) {
        if (*src == '+') {
            *dst++ = ' ';
            src++;
        } else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2])) {
            char hex[3] = {src[1], src[2], '\0'};
            *dst++ = (char)strtol(hex, NULL, 16);
            src += 3;
        } else {
            *dst++ = *src++;
        }
    }
    *dst = '\0';
}

// Parse form data
static void parse_form_data(const char *body, char *username, char *password, size_t max_len) {
    char temp[4096];
    strcpy(temp, body);

    char *pair = strtok(temp, "&");
    while (pair) {
        char *eq = strchr(pair, '=');
        if (eq) {
            *eq = '\0';
            char *key = pair;
            char *value = eq + 1;
            url_decode(value);

            if (strcmp(key, "username") == 0) {
                strncpy(username, value, max_len - 1);
            } else if (strcmp(key, "password") == 0) {
                strncpy(password, value, max_len - 1);
            }
        }
        pair = strtok(NULL, "&");
    }
}

// Parse multipart/form-data
static void parse_multipart_form_data(const char *body, const char *boundary, char *username, char *password, int *is_admin, size_t max_len) {
    printf("[DEBUG] Parsing multipart, boundary: '%s'\n", boundary);
    char boundary_start[256];
    snprintf(boundary_start, sizeof(boundary_start), "--%s", boundary);
    const char *start = strstr(body, boundary_start);
    if (!start) {
        printf("[DEBUG] Boundary not found in body\n");
    }
    start += strlen(boundary_start) + 2; // \r\n

    while (1) {
        const char *end = strstr(start, boundary_start);
        if (!end) break;
        // parse part
        const char *part_end = end; // include boundary
        size_t part_len = part_end - start;
        if (part_len > 1024) part_len = 1024; // limit
        char part[1025];
        memcpy(part, start, part_len);
        part[part_len] = '\0';
        printf("[DEBUG] Part: '%s'\n", part);
        // find name
        const char *disp = strstr(part, "Content-Disposition: form-data; name=\"");
        if (disp) {
            disp += 38;
            const char *quote = strchr(disp, '"');
            if (quote) {
                size_t key_len = quote - disp;
                char key[50] = "";
                if (key_len < sizeof(key)) {
                    memcpy(key, disp, key_len);
                    key[key_len] = '\0';
                    printf("[DEBUG] Key: '%s'\n", key);
                    // value after \r\n\r\n
                    const char *value_start = strstr(quote + 1, "\r\n\r\n");
                    if (value_start) {
                        value_start += 4;
                        const char *value_end = strstr(value_start, "\r\n");
                        if (value_end) {
                            size_t value_len = value_end - value_start;
                            printf("[DEBUG] Value len: %zu, value: '%.*s'\n", value_len, (int)value_len, value_start);
                            if (strcmp(key, "username") == 0) {
                                if (value_len < max_len) {
                                    memcpy(username, value_start, value_len);
                                    username[value_len] = '\0';
                                    printf("[DEBUG] Set username to '%s'\n", username);
                                }
                            } else if (strcmp(key, "password") == 0) {
                                if (value_len < max_len) {
                                    memcpy(password, value_start, value_len);
                                    password[value_len] = '\0';
                                    printf("[DEBUG] Set password to '%s'\n", password);
                                }
                            } else if (strcmp(key, "is_admin") == 0) {
                                *is_admin = 1;
                                printf("[DEBUG] Set is_admin to 1\n");
                            }
                        }
                    }
                }
            }
        }
        start = end + strlen(boundary_start) + 2;
        if (strncmp(start, "--", 2) == 0) break; // end
    }
    printf("[DEBUG] Final: username='%s', password='%s', is_admin=%d\n", username, password, *is_admin);
}

// Get cookie value
static const char *get_cookie(const char *headers, const char *name) {
    static char value[256];
    char cookie_header[1024];
    const char *cookie_start = strstr(headers, "Cookie:");
    if (!cookie_start) return NULL;

    strcpy(cookie_header, cookie_start + 7);
    char *end = strchr(cookie_header, '\r');
    if (end) *end = '\0';

    char *token = strtok(cookie_header, "; ");
    while (token) {
        char *eq = strchr(token, '=');
        if (eq) {
            *eq = '\0';
            if (strcmp(token, name) == 0) {
                strcpy(value, eq + 1);
                return value;
            }
        }
        token = strtok(NULL, "; ");
    }
    return NULL;
}

// Send HTTP response
static void send_response(int client_fd, int status_code, const char *status_text,
                          const char *content_type, const char *body, size_t body_len,
                          const char *set_cookie, const char *extra_header) {
    if (global_config && global_config->debug_web && strcmp(content_type, "application/octet-stream") != 0) {
        printf("[WEB-DEBUG] Sending response: %d %s, Content-Type: %s, Length: %zu\n",
               status_code, status_text, content_type, body_len);
        if (set_cookie) {
            printf("[WEB-DEBUG] Set-Cookie: %s\n", set_cookie);
        }
        if (extra_header) {
            printf("[WEB-DEBUG] Extra header: %s\n", extra_header);
        }
    }

    char response[8192];
    char date[64];
    time_t now = time(NULL);
    strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));

    int len = snprintf(response, sizeof(response),
        "HTTP/1.1 %d %s\r\n"
        "Date: %s\r\n"
        "Server: wssshd/1.0\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %zu\r\n"
        "Connection: keep-alive\r\n",
        status_code, status_text, date, content_type, body_len);

    if (extra_header) {
        len += snprintf(response + len, sizeof(response) - len, "%s\r\n", extra_header);
    }

    if (set_cookie) {
        len += snprintf(response + len, sizeof(response) - len, "Set-Cookie: %s\r\n", set_cookie);
    }

    len += snprintf(response + len, sizeof(response) - len, "\r\n");

    send(client_fd, response, len, 0);
    if (body && body_len > 0) {
        send(client_fd, body, body_len, 0);
    }
}

// Generate dynamic HTML for index page
static char *generate_index_html(const char *username, int is_admin, const char *error) {
    static char html[32768];
    char client_list[8192] = "";

    // Get registered clients
    for (size_t i = 0; i < global_state->clients_count; i++) {
        char client_html[2048];
        char services_html[1024] = "";
        char actions_html[512] = "";

        // Parse services (comma-separated)
        char services_copy[256];
        strcpy(services_copy, global_state->clients[i].services);
        char *service = strtok(services_copy, ",");
        while (service) {
            // Trim whitespace
            while (*service == ' ') service++;
            char *end = service + strlen(service) - 1;
            while (end > service && *end == ' ') *end-- = '\0';

            if (strcmp(service, "ssh") == 0) {
                strcat(actions_html, "<a href=\"/terminal/");
                strcat(actions_html, global_state->clients[i].client_id);
                strcat(actions_html, "\" class=\"btn btn-primary btn-sm me-1\">"
                    "<i class=\"fas fa-terminal\"></i> SSH</a>");
            } else if (strcmp(service, "vnc") == 0) {
                strcat(actions_html, "<a href=\"/vnc/");
                strcat(actions_html, global_state->clients[i].client_id);
                strcat(actions_html, "\" class=\"btn btn-info btn-sm me-1\">"
                    "<i class=\"fas fa-desktop\"></i> VNC</a>");
            } else if (strcmp(service, "rdp") == 0) {
                strcat(actions_html, "<a href=\"/rdp/");
                strcat(actions_html, global_state->clients[i].client_id);
                strcat(actions_html, "\" class=\"btn btn-primary btn-sm me-1\">"
                    "<i class=\"fas fa-windows\"></i> RDP</a>");
            }

            if (strlen(services_html) > 0) strcat(services_html, ", ");
            strcat(services_html, service);
            service = strtok(NULL, ",");
        }

        if (strlen(services_html) == 0) {
            strcpy(services_html, "No services configured");
        }

        const char *status_icon = global_state->clients[i].active ? "fa-desktop text-success" : "fa-server text-warning";
        const char *status_text = global_state->clients[i].active ? "Connected" : "Registered";

        snprintf(client_html, sizeof(client_html),
            "<div class=\"col-md-4 mb-3\">"
            "<div class=\"card client-card h-100\">"
            "<div class=\"card-body text-center\">"
            "<i class=\"fas %s fa-3x mb-3\"></i>"
            "<h5 class=\"card-title\">%s</h5>"
            "<p class=\"card-text text-muted\">%s</p>"
            "<p class=\"card-text small\">Services: %s</p>"
            "<div class=\"d-flex justify-content-center\">%s</div>"
            "</div></div></div>",
            status_icon,
            global_state->clients[i].client_id,
            status_text,
            services_html,
            actions_html);
        strcat(client_list, client_html);
    }

    if (strlen(client_list) == 0) {
        strcpy(client_list,
            "<div class=\"text-center py-5\">"
            "<i class=\"fas fa-server fa-4x text-muted mb-3\"></i>"
            "<h4 class=\"text-muted\">No clients registered</h4>"
            "<p class=\"text-muted\">Clients will appear here when they register.</p>"
            "</div>");
    }

    char admin_actions[1024] = "";
    if (is_admin) {
        strcpy(admin_actions,
            "<a href=\"/users\" class=\"btn btn-outline-primary btn-sm mb-2 w-100\">"
            "<i class=\"fas fa-users\"></i> Manage Users</a>");
    }

    // Add notification area
    char notification_area[1024] = "<div id=\"notification-area\" class=\"position-fixed top-0 end-0 p-3\" style=\"z-index: 1050;\">";
    if (error && strlen(error) > 0) {
        char error_notification[512];
        snprintf(error_notification, sizeof(error_notification), "<div class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">%s<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button></div>", error);
        strcat(notification_area, error_notification);
    }
    strcat(notification_area, "</div>");

    snprintf(html, sizeof(html), index_page_html,
        username, notification_area, client_list, admin_actions, global_config->port,
        global_config->domain ? global_config->domain : "N/A", global_state->clients_count);

    return html;
}

// Generate dynamic HTML for users page
static int generate_users_html(const char *username, char *html, size_t max_len) {
    web_user_t users[MAX_USERS];
    int user_count = db_get_all_users(users, MAX_USERS);
    if (user_count < 0) {
        return snprintf(html, max_len, "Error loading users");
    }

    char user_rows[8192] = "";
    for (int i = 0; i < user_count; i++) {
        char role_badge[64];
        if (users[i].is_admin) {
            strcpy(role_badge, "<span class=\"badge bg-danger\">Admin</span>");
        } else {
            strcpy(role_badge, "<span class=\"badge bg-secondary\">User</span>");
        }

        char row[1024];
        snprintf(row, sizeof(row),
            "<tr>"
            "<td>%s</td>"
            "<td>%s</td>"
            "<td>"
            "<button class=\"btn btn-sm btn-outline-primary\" onclick=\"editUser(%d, '%s', %s)\">"
            "<i class=\"fas fa-edit\"></i> Edit</button>"
            "<button class=\"btn btn-sm btn-outline-danger ms-1\" onclick=\"deleteUser(%d, '%s')\">"
            "<i class=\"fas fa-trash\"></i> Delete</button>"
            "</td>"
            "</tr>",
            users[i].username, role_badge, users[i].id, users[i].username,
            users[i].is_admin ? "true" : "false", users[i].id, users[i].username);
        strcat(user_rows, row);
    }

    return snprintf(html, max_len, users_page_html, username, user_rows);
}

// Handle HTTP requests
// Returns 1 if the connection should be kept open (WebSocket), 0 if it should be closed
static int handle_request(int client_fd, const http_request_t *req) {
    if (global_config && global_config->debug_web && !strstr(req->path, "/xterm/data")) {
        printf("[WEB-DEBUG] Received %s request for %s\n", req->method, req->path);
        if (req->query[0]) {
            printf("[WEB-DEBUG] Query: %s\n", req->query);
        }
        printf("[WEB-DEBUG] Headers: %s\n", req->headers);
        if (req->content_length > 0) {
            printf("[WEB-DEBUG] Content-Length: %d\n", req->content_length);
        }
    }

    const char *session_id = get_cookie(req->headers, "session_id");
    const char *username = NULL;
    int is_admin = 0;

    if (global_config && global_config->debug_web && !strstr(req->path, "/xterm/data")) {
        printf("[WEB-DEBUG] Session ID: %s\n", session_id ? session_id : "none");
    }

    if (session_id) {
        username = validate_session(session_id);
        if (username) {
            web_user_t *user = find_user(username);
            if (user) is_admin = user->is_admin;
        }
    }

    if (global_config && global_config->debug_web && !strstr(req->path, "/xterm/data")) {
        printf("[WEB-DEBUG] Authenticated user: %s (admin: %s)\n",
               username ? username : "none", is_admin ? "yes" : "no");
    }

    // Route handling
    // Handle WebSocket terminal, VNC, and RDP connections first
    char *headers_copy = NULL;
    printf("[DEBUG] Checking path: %s\n", req->path);
    if ((strncmp(req->path, "/terminal/", 9) == 0 && strstr(req->path, "/ws")) ||
        (strncmp(req->path, "/vnc/", 5) == 0 && strstr(req->path, "/ws")) ||
        (strncmp(req->path, "/rdp/", 5) == 0 && strstr(req->path, "/ws"))) {
        printf("[WEBSOCKET] WebSocket upgrade request detected for path: %s\n", req->path);

        // WebSocket terminal, VNC, or RDP connection
        char path_copy[1024];
        strcpy(path_copy, req->path);
        char *client_id;
        bool is_vnc = false;
        bool is_rdp = false;
        if (strncmp(req->path, "/vnc/", 5) == 0) {
            client_id = path_copy + 5;
            is_vnc = true;
        } else if (strncmp(req->path, "/rdp/", 5) == 0) {
            client_id = path_copy + 5;
            is_rdp = true;
        } else {
            client_id = path_copy + 9;
        }
        // Skip any leading slashes
        while (*client_id == '/') client_id++;
        char *ws_part = strstr(client_id, "/ws");
        if (ws_part) *ws_part = '\0'; // Remove /ws from client_id

        printf("[WEBSOCKET] Extracted client_id: %s\n", client_id);

        if (!username) {
            printf("[WEBSOCKET] No authenticated user, rejecting\n");
            send_response(client_fd, 401, "Unauthorized", "text/plain", "Authentication required", 21, NULL, NULL);
            return 0;
        }

        printf("[WEBSOCKET] User authenticated: %s\n", username);

        // Check if this is a WebSocket upgrade request
        const char *upgrade = NULL;
        const char *connection = NULL;
        const char *sec_websocket_key = NULL;

        // Parse headers manually since they contain full header lines
        headers_copy = strdup(req->headers);
        if (!headers_copy) {
            send_response(client_fd, 500, "Internal Server Error", "text/plain", "Memory allocation failed", 23, NULL, NULL);
            return 0;
        }

        char *header_line = strtok(headers_copy, "\r\n");
        while (header_line) {
            if (strncasecmp(header_line, "Upgrade:", 8) == 0) {
                upgrade = header_line + 8;
                while (*upgrade == ' ' || *upgrade == '\t') upgrade++;
            } else if (strncasecmp(header_line, "Connection:", 11) == 0) {
                connection = header_line + 11;
                while (*connection == ' ' || *connection == '\t') connection++;
            } else if (strncasecmp(header_line, "Sec-WebSocket-Key:", 18) == 0) {
                sec_websocket_key = header_line + 18;
                while (*sec_websocket_key == ' ' || *sec_websocket_key == '\t') sec_websocket_key++;
            }
            header_line = strtok(NULL, "\r\n");
        }

        if (global_config && global_config->debug_web) {
            printf("[WEB-DEBUG] WebSocket upgrade check: upgrade='%s', connection='%s', key='%s'\n",
                   upgrade ? upgrade : "NULL", connection ? connection : "NULL",
                   sec_websocket_key ? "(present)" : "NULL");
        }

        bool is_valid_upgrade = upgrade && strcasecmp(upgrade, "websocket") == 0 &&
                               connection && strstr(connection, "Upgrade") &&
                               sec_websocket_key;

        if (global_config && global_config->debug_web) {
            printf("[WEB-DEBUG] WebSocket upgrade valid: %s\n", is_valid_upgrade ? "yes" : "no");
        }

        if (is_valid_upgrade) {
            // Web interface WebSockets can use non-TLS for noVNC/mstsc.js connections

            // Set socket to non-blocking mode for WebSocket
            int flags = fcntl(client_fd, F_GETFL, 0);
            if (flags != -1) {
                fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
            }

            // Create WebSocket connection (plain, no SSL for web interface)
            ws_connection_t *ws_conn = ws_connection_create_plain(client_fd);
            if (!ws_conn) {
                free(headers_copy);
                send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to create WebSocket connection", 35, NULL, NULL);
                return 0;
            }

            // Compute WebSocket accept key
            char *accept_key = ws_compute_accept_key(sec_websocket_key);
            if (!accept_key) {
                ws_connection_free(ws_conn);
                free(headers_copy);
                send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to compute WebSocket accept key", 36, NULL, NULL);
                return 0;
            }

            // Send WebSocket handshake response
            char response[512];
            int response_len;
            if (is_vnc) {
                response_len = snprintf(response, sizeof(response),
                    "HTTP/1.1 101 Switching Protocols\r\n"
                    "Upgrade: websocket\r\n"
                    "Connection: Upgrade\r\n"
                    "Sec-WebSocket-Accept: %s\r\n"
                    "Sec-WebSocket-Protocol: binary\r\n"
                    "\r\n", accept_key);
            } else {
                response_len = snprintf(response, sizeof(response),
                    "HTTP/1.1 101 Switching Protocols\r\n"
                    "Upgrade: websocket\r\n"
                    "Connection: Upgrade\r\n"
                    "Sec-WebSocket-Accept: %s\r\n"
                    "\r\n", accept_key);
            }

            free(accept_key);

            int bytes_written = write(client_fd, response, response_len);
            if (bytes_written != response_len) {
                ws_connection_free(ws_conn);
                free(headers_copy);
                send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to send WebSocket handshake response", 42, NULL, NULL);
                return 0;
            }

            // Set connection state to open
            ws_conn->state = WS_STATE_OPEN;

            if (is_vnc) {
                // Create WebSocket VNC connection
                websocket_vnc_conn_t *ws_vnc_conn = add_websocket_vnc_connection(ws_conn, "", client_id, username);
                if (!ws_vnc_conn) {
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Too many WebSocket connections", 30, NULL, NULL);
                    return 0;
                }

                // Start WebSocket VNC handler thread
                if (pthread_create(&ws_vnc_conn->thread, NULL, websocket_vnc_handler, ws_vnc_conn) != 0) {
                    remove_websocket_vnc_connection("");
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to start WebSocket handler", 32, NULL, NULL);
                    return 0;
                }

                pthread_detach(ws_vnc_conn->thread);
            } else if (is_rdp) {
                // Create WebSocket RDP connection
                websocket_rdp_conn_t *ws_rdp_conn = add_websocket_rdp_connection(ws_conn, "", client_id, username);
                if (!ws_rdp_conn) {
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Too many WebSocket connections", 30, NULL, NULL);
                    return 0;
                }

                // Start WebSocket RDP handler thread
                if (pthread_create(&ws_rdp_conn->thread, NULL, websocket_rdp_handler, ws_rdp_conn) != 0) {
                    remove_websocket_rdp_connection("");
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to start WebSocket handler", 32, NULL, NULL);
                    return 0;
                }

                pthread_detach(ws_rdp_conn->thread);
            } else {
                // Create WebSocket terminal connection
                websocket_terminal_conn_t *ws_term_conn = add_websocket_connection(ws_conn, "", client_id, username);
                if (!ws_term_conn) {
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Too many WebSocket connections", 30, NULL, NULL);
                    return 0;
                }

                // Start WebSocket handler thread
                if (pthread_create(&ws_term_conn->thread, NULL, websocket_terminal_handler, ws_term_conn) != 0) {
                    remove_websocket_connection("");
                    ws_connection_free(ws_conn);
                    send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to start WebSocket handler", 32, NULL, NULL);
                    return 0;
                }

                pthread_detach(ws_term_conn->thread);
            }
            return 1; // WebSocket connection is now handled by the thread, keep socket open
        } else {
            send_response(client_fd, 400, "Bad Request", "text/plain", "Invalid WebSocket upgrade request", 32, NULL, NULL);
            free(headers_copy);
            return 0;
        }
    } else {
        free(headers_copy);
    }

    // Handle terminal routes (both GET and POST)
    if (strncmp(req->path, "/terminal/", 9) == 0) {
        // Extract client_id and action from path
        char path_copy[1024];
        strcpy(path_copy, req->path);
        char *client_id = path_copy + 9;
        // Skip any leading slashes
        while (*client_id == '/') client_id++;
        char *action = strchr(client_id, '/');
        if (action) {
            *action = '\0';
            action++;
            // Handle xterm prefix
            if (strncmp(action, "xterm/", 6) == 0) {
                action += 6;
            }
        }

        if (global_config && global_config->debug_web && (!action || strcmp(action, "data") != 0)) {
            printf("[WEB-DEBUG] Terminal request: path=%s, client_id=%s, action=%s\n", req->path, client_id, action ? action : "(null)");
        }

        if (!username) {
            send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
            return 0;
        }

        if (!action || *action == '\0') {
            // GET /terminal/{client_id} - show terminal page
            // Check if client exists and has SSH service
            int client_has_ssh = 0;
            int client_found = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (global_state->clients[i].active &&
                    strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_found = 1;
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Found client %s, active=%d, services='%s'\n", client_id, global_state->clients[i].active, global_state->clients[i].services);
                    }
                    // Check if SSH is in services
                    if (global_state->clients[i].services[0] != '\0') {
                        char services_copy[256];
                        strcpy(services_copy, global_state->clients[i].services);
                        char *service = strtok(services_copy, ",");
                        while (service) {
                            while (*service == ' ') service++;
                            char *end = service + strlen(service) - 1;
                            while (end > service && *end == ' ') *end-- = '\0';
                            if (strcmp(service, "ssh") == 0) {
                                client_has_ssh = 1;
                                break;
                            }
                            service = strtok(NULL, ",");
                        }
                    }
                    break;
                }
            }
            if (global_config && global_config->debug_web) {
                printf("[WEB-DEBUG] Client %s found=%d, has_ssh=%d\n", client_id, client_found, client_has_ssh);
            }
            if (!client_has_ssh) {
                // Redirect to dashboard with error notification
                char location_header[2048];
                snprintf(location_header, sizeof(location_header), "Location: /?error=SSH service not available for client %s", client_id);
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
                return 0;
            }
            // Generate terminal HTML with client_id
            char html[32768];
            int len = snprintf(html, sizeof(html), terminal_page_html,
                client_id, client_id, client_id, client_id, client_id, client_id, client_id, client_id, client_id, client_id);
            send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
        } else {
            // Handle terminal actions (connect, data, disconnect, resize)
            // Check if client exists
            int client_exists = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (global_state->clients[i].active &&
                    strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_exists = 1;
                    break;
                }
            }
            if (global_config && global_config->debug_web && strcmp(action, "data") != 0) {
                printf("[WEB-DEBUG] Terminal action '%s' for client %s, client_exists=%d\n", action, client_id, client_exists);
            }
            if (!client_exists) {
                send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Client not connected\"}", 31, NULL, NULL);
                return 0;
            }

            if (strcmp(req->method, "POST") == 0) {
                if (strcmp(action, "connect") == 0) {
                    // Create terminal session
                    char form_username[50] = "root";
                    int cols = 80, rows = 24;  // Used in terminal_create_session call

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "username") == 0) {
                                strncpy(form_username, value, sizeof(form_username) - 1);
                            } else if (strcmp(key, "cols") == 0) {
                                cols = atoi(value);
                            } else if (strcmp(key, "rows") == 0) {
                                rows = atoi(value);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    terminal_session_t *session = terminal_create_session(global_config, form_username, client_id);
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Created terminal session for %s@%s, command: %s\n", form_username, client_id, session->command);
                    }
                    if (session) {
                        // Resize terminal to requested dimensions
                        terminal_resize(session, cols, rows);

                        pthread_mutex_lock(&terminals_mutex);
                        if (active_terminals_count < MAX_ACTIVE_TERMINALS) {
                            active_terminals[active_terminals_count++] = session;

                            // Update WebSocket connection with request_id
                            // Find WebSocket connection by client_id (since request_id is empty initially)
                            pthread_mutex_lock(&websocket_mutex);
                            websocket_terminal_conn_t *ws_conn = NULL;
                            for (int i = 0; i < websocket_connections_count; i++) {
                                if (websocket_connections[i].active &&
                                    strcmp(websocket_connections[i].client_id, client_id) == 0) {
                                    ws_conn = &websocket_connections[i];
                                    break;
                                }
                            }
                            if (ws_conn) {
                                size_t len = strlen(session->request_id);
                                if (len >= sizeof(ws_conn->request_id)) {
                                    len = sizeof(ws_conn->request_id) - 1;
                                }
                                memcpy(ws_conn->request_id, session->request_id, len);
                                ws_conn->request_id[len] = '\0';
                                printf("Updated WebSocket connection for client %s with request_id: %s\n", client_id, session->request_id);
                            } else {
                                printf("Warning: Could not find WebSocket connection for client %s\n", client_id);
                            }
                            pthread_mutex_unlock(&websocket_mutex);

                            char json[256];
                            int len = snprintf(json, sizeof(json), "{\"request_id\":\"%s\",\"command\":\"%s\"}",
                                              session->request_id, session->command);
                            pthread_mutex_unlock(&terminals_mutex);
                            send_response(client_fd, 200, "OK", "application/json", json, len, NULL, NULL);
                        } else {
                            terminal_free_session(session);
                            pthread_mutex_unlock(&terminals_mutex);
                            send_response(client_fd, 500, "Internal Server Error", "application/json",
                                         "{\"error\":\"Too many active terminals\"}", 36, NULL, NULL);
                        }
                    } else {
                        send_response(client_fd, 500, "Internal Server Error", "application/json",
                                     "{\"error\":\"Failed to create terminal session\"}", 45, NULL, NULL);
                    }
                    return 0;
                } else if (strcmp(action, "data") == 0) {
                    char request_id[37] = "";
                    char data[4096] = "";

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "request_id") == 0) {
                                strncpy(request_id, value, sizeof(request_id) - 1);
                            } else if (strcmp(key, "data") == 0) {
                                strcpy(data, value);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    pthread_mutex_lock(&terminals_mutex);
                    int found = 0;
                    for (int i = 0; i < active_terminals_count; i++) {
                        if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
                            if (terminal_send_data(active_terminals[i], data)) {
                                found = 1;
                            }
                            break;
                        }
                    }
                    pthread_mutex_unlock(&terminals_mutex);

                    if (found) {
                        send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
                    } else {
                        send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Terminal session not found\"}", 37, NULL, NULL);
                    }
                    return 0;
                    return 0;
                    return 0;
                } else if (strcmp(action, "disconnect") == 0) {
                    char request_id[37] = "";

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "request_id") == 0) {
                                strncpy(request_id, value, sizeof(request_id) - 1);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    pthread_mutex_lock(&terminals_mutex);
                    for (int i = 0; i < active_terminals_count; i++) {
                        if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
                            terminal_disconnect(active_terminals[i]);
                            terminal_free_session(active_terminals[i]);
                            memmove(&active_terminals[i], &active_terminals[i + 1],
                                   sizeof(terminal_session_t *) * (active_terminals_count - i - 1));
                            active_terminals_count--;
                            break;
                        }
                    }
                    pthread_mutex_unlock(&terminals_mutex);

                    send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
                } else if (strcmp(action, "resize") == 0) {
                    char request_id[37] = "";
                    int cols = 80, rows = 24;

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "request_id") == 0) {
                                strncpy(request_id, value, sizeof(request_id) - 1);
                            } else if (strcmp(key, "cols") == 0) {
                                cols = atoi(value);
                            } else if (strcmp(key, "rows") == 0) {
                                rows = atoi(value);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    pthread_mutex_lock(&terminals_mutex);
                    int found = 0;
                    for (int i = 0; i < active_terminals_count; i++) {
                        if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
                            terminal_resize(active_terminals[i], cols, rows);
                            found = 1;
                            break;
                        }
                    }
                    pthread_mutex_unlock(&terminals_mutex);

                    if (found) {
                        send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
                    } else {
                        send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Terminal session not found\"}", 37, NULL, NULL);
                    }
                } else {
                    send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
                }
            } else if (strcmp(req->method, "GET") == 0) {
                if (strcmp(action, "data") == 0) {
                    char request_id[37] = "";
                    const char *query = req->query;
                    if (query) {
                        char query_copy[1024];
                        strcpy(query_copy, query);
                        char *token = strtok(query_copy, "&");
                        while (token) {
                            char *eq = strchr(token, '=');
                            if (eq) {
                                *eq = '\0';
                                if (strcmp(token, "request_id") == 0) {
                                    strcpy(request_id, eq + 1);
                                }
                            }
                            token = strtok(NULL, "&");
                        }
                    }

                    pthread_mutex_lock(&terminals_mutex);
                    char *output = NULL;
                    size_t output_len = 0;
                    bool session_ended = false;
                    for (int i = 0; i < active_terminals_count; i++) {
                        if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
                            if (!terminal_is_running(active_terminals[i])) {
                                session_ended = true;
                            } else {
                                output = terminal_get_output(active_terminals[i], &output_len);
                            }
                            break;
                        }
                    }
                    pthread_mutex_unlock(&terminals_mutex);

                    if (session_ended) {
                        send_response(client_fd, 200, "OK", "application/json; charset=utf-8", ended_json, sizeof(ended_json) - 1, NULL, NULL);
                    } else if (output) {
                        send_response(client_fd, 200, "OK", "application/octet-stream", output, output_len, NULL, NULL);
                        free(output);
                    } else {
                        send_response(client_fd, 200, "OK", "application/octet-stream", "", 0, NULL, NULL);
                    }
                    // Skip debug for data responses to avoid spam
                } else {
                    send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
                }
            } else {
                send_response(client_fd, 405, "Method Not Allowed", "text/plain", "Method not allowed", 18, NULL, NULL);
            }
        }
        return 0;
    }

    // Handle VNC routes (both GET and POST)
    if (strncmp(req->path, "/vnc/", 5) == 0) {
        // Extract client_id and action from path
        char path_copy[1024];
        strcpy(path_copy, req->path);
        char *client_id = path_copy + 5;
        // Skip any leading slashes
        while (*client_id == '/') client_id++;
        char *action = strchr(client_id, '/');
        if (action) {
            *action = '\0';
            action++;
        }

        if (global_config && global_config->debug_web && (!action || strcmp(action, "data") != 0)) {
            printf("[WEB-DEBUG] VNC request: path=%s, client_id=%s, action=%s\n", req->path, client_id, action ? action : "(null)");
        }

        if (!username) {
            send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
            return 0;
        }

        if (!action || *action == '\0') {
            // GET /vnc/{client_id} - show VNC page
            // Check if client exists and has VNC service
            int client_has_vnc = 0;
            int client_found = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_found = 1;
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Found client %s, active=%d, services='%s'\n", client_id, global_state->clients[i].active, global_state->clients[i].services);
                    }
                    // Check if VNC is in services
                    char services_copy[256];
                    strcpy(services_copy, global_state->clients[i].services);
                    char *service = strtok(services_copy, ",");
                    while (service) {
                        while (*service == ' ') service++;
                        char *end = service + strlen(service) - 1;
                        while (end > service && *end == ' ') *end-- = '\0';
                        if (strcmp(service, "vnc") == 0) {
                            client_has_vnc = 1;
                            break;
                        }
                        service = strtok(NULL, ",");
                    }
                    break;
                }
            }
            if (global_config && global_config->debug_web) {
                printf("[WEB-DEBUG] Client %s found=%d, has_vnc=%d\n", client_id, client_found, client_has_vnc);
            }
            if (!client_has_vnc) {
                // Redirect to dashboard with error notification
                char location_header[2048];
                snprintf(location_header, sizeof(location_header), "Location: /?error=VNC service not available for client %s", client_id);
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
                return 0;
            }
            // Generate VNC HTML with client_id
            char html[32768];
            int len = snprintf(html, sizeof(html), vnc_page_html,
                client_id, client_id, client_id, client_id, client_id);
            send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
        } else {
            // Handle VNC actions (connect, disconnect)
            // Check if client exists
            int client_exists = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (global_state->clients[i].active &&
                    strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_exists = 1;
                    break;
                }
            }
            if (global_config && global_config->debug_web && strcmp(action, "data") != 0) {
                printf("[WEB-DEBUG] VNC action '%s' for client %s, client_exists=%d\n", action, client_id, client_exists);
            }
            if (!client_exists) {
                send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Client not connected\"}", 31, NULL, NULL);
                return 0;
            }

            if (strcmp(req->method, "POST") == 0) {
                if (strcmp(action, "disconnect") == 0) {
                    char request_id[37] = "";

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "request_id") == 0) {
                                strncpy(request_id, value, sizeof(request_id) - 1);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    pthread_mutex_lock(&vncs_mutex);
                    for (int i = 0; i < active_vncs_count; i++) {
                        if (strcmp(active_vncs[i]->request_id, request_id) == 0) {
                            vnc_disconnect(active_vncs[i]);
                            vnc_free_session(active_vncs[i]);
                            memmove(&active_vncs[i], &active_vncs[i + 1],
                                   sizeof(vnc_session_t *) * (active_vncs_count - i - 1));
                            active_vncs_count--;
                            break;
                        }
                    }
                    pthread_mutex_unlock(&vncs_mutex);

                    send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
                } else {
                    send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
                }
            } else {
                send_response(client_fd, 405, "Method Not Allowed", "text/plain", "Method not allowed", 18, NULL, NULL);
            }
        }
        return 0;
    }

    // Handle RDP routes (both GET and POST)
    if (strncmp(req->path, "/rdp/", 5) == 0) {
        // Extract client_id and action from path
        char path_copy[1024];
        strcpy(path_copy, req->path);
        char *client_id = path_copy + 5;
        // Skip any leading slashes
        while (*client_id == '/') client_id++;
        char *action = strchr(client_id, '/');
        if (action) {
            *action = '\0';
            action++;
        }

        if (global_config && global_config->debug_web && (!action || strcmp(action, "data") != 0)) {
            printf("[WEB-DEBUG] RDP request: path=%s, client_id=%s, action=%s\n", req->path, client_id, action ? action : "(null)");
        }

        if (!username) {
            send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
            return 0;
        }

        if (!action || *action == '\0') {
            // GET /rdp/{client_id} - show RDP page
            // Check if client exists and has RDP service
            int client_has_rdp = 0;
            int client_found = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_found = 1;
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Found client %s, active=%d, services='%s'\n", client_id, global_state->clients[i].active, global_state->clients[i].services);
                    }
                    // Check if RDP is in services
                    char services_copy[256];
                    strcpy(services_copy, global_state->clients[i].services);
                    char *service = strtok(services_copy, ",");
                    while (service) {
                        while (*service == ' ') service++;
                        char *end = service + strlen(service) - 1;
                        while (end > service && *end == ' ') *end-- = '\0';
                        if (strcmp(service, "rdp") == 0) {
                            client_has_rdp = 1;
                            break;
                        }
                        service = strtok(NULL, ",");
                    }
                    break;
                }
            }
            if (global_config && global_config->debug_web) {
                printf("[WEB-DEBUG] Client %s found=%d, has_rdp=%d\n", client_id, client_found, client_has_rdp);
            }
            if (!client_has_rdp) {
                // Redirect to dashboard with error notification
                char location_header[2048];
                snprintf(location_header, sizeof(location_header), "Location: /?error=RDP service not available for client %s", client_id);
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
                return 0;
            }
            // Generate RDP HTML with client_id
            char html[65536];
            int len = snprintf(html, sizeof(html), rdp_page_html,
                client_id, client_id, client_id, client_id, client_id);
            // No CSP header for RDP pages to avoid blocking WebAssembly
            send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
        } else {
            // Handle RDP actions (connect, disconnect)
            // Check if client exists
            int client_exists = 0;
            for (size_t i = 0; i < global_state->clients_count; i++) {
                if (global_state->clients[i].active &&
                    strcmp(global_state->clients[i].client_id, client_id) == 0) {
                    client_exists = 1;
                    break;
                }
            }
            if (global_config && global_config->debug_web && strcmp(action, "data") != 0) {
                printf("[WEB-DEBUG] RDP action '%s' for client %s, client_exists=%d\n", action, client_id, client_exists);
            }
            if (!client_exists) {
                send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Client not connected\"}", 31, NULL, NULL);
                return 0;
            }

            if (strcmp(req->method, "POST") == 0) {
                if (strcmp(action, "disconnect") == 0) {
                    char request_id[37] = "";

                    // Parse form data
                    char temp_body[4096];
                    strcpy(temp_body, req->body);
                    char *pair = strtok(temp_body, "&");
                    while (pair) {
                        char *eq = strchr(pair, '=');
                        if (eq) {
                            *eq = '\0';
                            char *key = pair;
                            char *value = eq + 1;
                            url_decode(value);
                            if (strcmp(key, "request_id") == 0) {
                                strncpy(request_id, value, sizeof(request_id) - 1);
                            }
                        }
                        pair = strtok(NULL, "&");
                    }

                    pthread_mutex_lock(&rdps_mutex);
                    for (int i = 0; i < active_rdps_count; i++) {
                        if (strcmp(active_rdps[i]->request_id, request_id) == 0) {
                            rdp_disconnect(active_rdps[i]);
                            rdp_free_session(active_rdps[i]);
                            memmove(&active_rdps[i], &active_rdps[i + 1],
                                   sizeof(rdp_session_t *) * (active_rdps_count - i - 1));
                            active_rdps_count--;
                            break;
                        }
                    }
                    pthread_mutex_unlock(&rdps_mutex);

                    send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
                } else {
                    send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
                }
            } else {
                send_response(client_fd, 405, "Method Not Allowed", "text/plain", "Method not allowed", 18, NULL, NULL);
            }
        }
        return 0;
    }

    if (strcmp(req->method, "GET") == 0) {
        if (strcmp(req->path, "/") == 0 || strcmp(req->path, "/index.html") == 0) {
            if (!username) {
                // Redirect to login
                char location_header[256];
                snprintf(location_header, sizeof(location_header), "Location: /login");
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
                return 0;
            }
            char error[256] = "";
            if (req->query[0]) {
                // Parse error parameter
                if (strstr(req->query, "error=")) {
                    char *error_start = strstr(req->query, "error=") + 6;
                    // Copy and URL decode the error message
                    strncpy(error, error_start, sizeof(error) - 1);
                    error[sizeof(error) - 1] = '\0';
                    url_decode(error);
                }
            }
            char *html = generate_index_html(username, is_admin, error);
            send_response(client_fd, 200, "OK", "text/html", html, strlen(html), NULL, NULL);
        } else if (strcmp(req->path, "/login") == 0) {
            send_response(client_fd, 200, "OK", "text/html", login_page_html, strlen(login_page_html), NULL, NULL);
        } else if (strcmp(req->path, "/logout") == 0) {
            send_response(client_fd, 302, "Found", "text/html", NULL, 0, "session_id=; Max-Age=0; Path=/", "Location: /");
            return 0;
        } else if (strcmp(req->path, "/users") == 0) {
            if (!username || !is_admin) {
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
                return 0;
            }
            // Generate dynamic users page
            char html[32768];
            int len = generate_users_html(username, html, sizeof(html));
            send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
        } else if (strcmp(req->path, "/api/clients") == 0) {
            if (!username) {
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
                return 0;
            }
            char json[4096];
            int len = snprintf(json, sizeof(json), "{\"clients\":{");
            for (size_t i = 0; i < global_state->clients_count; i++) {
                len += snprintf(json + len, sizeof(json) - len, "\"%s\":{\"status\":\"%s\",\"services\":\"%s\"}",
                               global_state->clients[i].client_id,
                               global_state->clients[i].active ? "connected" : "registered",
                               global_state->clients[i].services);
                if (i < global_state->clients_count - 1) len += snprintf(json + len, sizeof(json) - len, ",");
            }
            len += snprintf(json + len, sizeof(json) - len, "},\"count\":%zu}", global_state->clients_count);
            send_response(client_fd, 200, "OK", "application/json", json, len, NULL, NULL);
        } else {
            // Try to serve embedded asset
            size_t size;
            const char *asset = get_embedded_asset(req->path, &size);
            if (asset) {
                const char *content_type = "text/plain";
                if (strstr(req->path, ".html")) content_type = "text/html";
                else if (strstr(req->path, ".css")) content_type = "text/css";
                else if (strstr(req->path, ".js")) content_type = "application/javascript";
                else if (strstr(req->path, ".wasm")) content_type = "application/wasm";
                else if (strstr(req->path, ".jpg") || strstr(req->path, ".jpeg")) content_type = "image/jpeg";
                else if (strstr(req->path, ".png")) content_type = "image/png";
                else if (strstr(req->path, ".ico")) content_type = "image/x-icon";
                send_response(client_fd, 200, "OK", content_type, asset, size, NULL, NULL);
            } else {
                send_response(client_fd, 404, "Not Found", "text/html", "Not found", 9, NULL, NULL);
            }
        }
    } else if (strcmp(req->method, "POST") == 0) {
        if (strcmp(req->path, "/login") == 0) {
            if (global_config && global_config->debug_web) {
                printf("[WEB-DEBUG] Login POST body (%d bytes): '%s'\n",
                       req->content_length, req->body);
            }

            char form_username[50] = "";
            char form_password[50] = "";
            parse_form_data(req->body, form_username, form_password, sizeof(form_username));

            if (global_config && global_config->debug_web) {
                printf("[WEB-DEBUG] Parsed login data: username='%s', password='%s'\n",
                       form_username, form_password[0] ? "***" : "(empty)");
            }

            web_user_t *user = find_user(form_username);
            if (user) {
                char hashed[100];
                simple_hash(form_password, hashed, sizeof(hashed));
                if (strcmp(user->password_hash, hashed) == 0) {
                    const char *new_session = create_session(form_username);
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Login successful for user '%s', session: %s\n",
                               form_username, new_session);
                    }
                    char cookie[256];
                    snprintf(cookie, sizeof(cookie), "session_id=%s; Path=/; HttpOnly", new_session);
                    send_response(client_fd, 302, "Found", "text/html", NULL, 0, cookie, "Location: /");
                    return 0;
                } else {
                    if (global_config && global_config->debug_web) {
                        printf("[WEB-DEBUG] Login failed: invalid password for user '%s'\n", form_username);
                    }
                }
            } else {
                if (global_config && global_config->debug_web) {
                    printf("[WEB-DEBUG] Login failed: user '%s' not found\n", form_username);
                }
            }
            const char *asset = get_embedded_asset("/login", NULL);
            send_response(client_fd, 200, "OK", "text/html", asset, strlen(asset), NULL, NULL);
        } else if (strcmp(req->path, "/add_user") == 0) {
            if (!username || !is_admin) {
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
                return 0;
            }

            char form_username[50] = "";
            char form_password[50] = "";
            int form_is_admin = 0;

            // Parse form data
            char *content_type = strstr(req->headers, "Content-Type:");
            if (content_type) {
                content_type += 13;
                while (*content_type == ' ' || *content_type == '\t') content_type++;
                if (strstr(content_type, "multipart/form-data")) {
                    char *boundary = strstr(content_type, "boundary=");
                    if (boundary) {
                        boundary += 9;
                        char boundary_str[256];
                        char *end = strchr(boundary, '\r');
                        if (end) *end = '\0';
                        strcpy(boundary_str, boundary);
                        parse_multipart_form_data(req->body, boundary_str, form_username, form_password, &form_is_admin, sizeof(form_username));
                    }
                } else {
                    // fallback to urlencoded
                    parse_form_data(req->body, form_username, form_password, sizeof(form_username));
                    // for is_admin, assume not, or parse
                    // but since it's multipart, skip
                }
            }
            if (!form_username[0] || !form_password[0]) {
                send_response(client_fd, 400, "Bad Request", "application/json", "{\"success\":false,\"error\":\"Username and password are required\"}", 62, NULL, NULL);
                return 0;
            }

            // Check if username already exists
            if (find_user(form_username)) {
                send_response(client_fd, 400, "Bad Request", "application/json", "{\"success\":false,\"error\":\"Username already exists\"}", 50, NULL, NULL);
                return 0;
            }

            char hashed[100];
            simple_hash(form_password, hashed, sizeof(hashed));

            if (db_add_user(form_username, hashed, form_is_admin) == 0) {
                send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
            } else {
                send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"success\":false,\"error\":\"Failed to add user\"}", 45, NULL, NULL);
            }
        } else if (strncmp(req->path, "/edit_user/", 11) == 0) {
            if (!username || !is_admin) {
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
                return 0;
            }

            int user_id = atoi(req->path + 11);

            char form_username[50] = "";
            char form_password[50] = "";
            int form_is_admin = 0;

            // Parse form data
            char temp_body[4096];
            strcpy(temp_body, req->body);
            char *pair = strtok(temp_body, "&");
            while (pair) {
                char *eq = strchr(pair, '=');
                if (eq) {
                    *eq = '\0';
                    char *key = pair;
                    char *value = eq + 1;
                    url_decode(value);
                    if (strcmp(key, "username") == 0) {
                        strncpy(form_username, value, sizeof(form_username) - 1);
                    } else if (strcmp(key, "password") == 0 && strlen(value) > 0) {
                        strncpy(form_password, value, sizeof(form_password) - 1);
                    } else if (strcmp(key, "is_admin") == 0) {
                        form_is_admin = 1;
                    }
                }
                pair = strtok(NULL, "&");
            }

            char *password_hash = NULL;
            if (strlen(form_password) > 0) {
                password_hash = malloc(100);
                simple_hash(form_password, password_hash, 100);
            }

            if (db_update_user(user_id, form_username, form_is_admin, password_hash) == 0) {
                send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
            } else {
                send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"success\":false,\"error\":\"Failed to update user\"}", 47, NULL, NULL);
            }

            if (password_hash) free(password_hash);
        } else if (strncmp(req->path, "/delete_user/", 13) == 0) {
            if (!username || !is_admin) {
                send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
                return 0;
            }

            int user_id = atoi(req->path + 13);

            if (db_delete_user(user_id) == 0) {
                send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
            } else {
                send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"success\":false,\"error\":\"Failed to delete user\"}", 47, NULL, NULL);
            }
        } else {
            send_response(client_fd, 405, "Method Not Allowed", "text/html", "Method not allowed", 18, NULL, NULL);
        }
    } else {
        send_response(client_fd, 405, "Method Not Allowed", "text/html", "Method not allowed", 18, NULL, NULL);
    }
    return 0;
}

// HTTP server thread function
static void *http_server_thread(void *arg __attribute__((unused))) {
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("Failed to create server socket");
        return NULL;
    }

    // Set socket options
    int opt = 1;
    setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(global_config->web_host);
    server_addr.sin_port = htons(global_config->web_port);

    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Failed to bind server socket");
        close(server_socket);
        return NULL;
    }

    if (listen(server_socket, 10) < 0) {
        perror("Failed to listen on server socket");
        close(server_socket);
        return NULL;
    }

    printf("Web interface starting on %s:%d\n", global_config->web_host, global_config->web_port);
    server_running = 1;

    while (server_running) {
        int client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
        if (client_fd < 0) {
            if (server_running) perror("Failed to accept client connection");
            continue;
        }

        http_request_t req;
        if (parse_http_request(client_fd, &req) == 0) {
            int keep_open = handle_request(client_fd, &req);
            if (keep_open) {
                // WebSocket connection - don't close the socket
                continue;
            }
        }

        close(client_fd);
    }

    close(server_socket);
    server_socket = -1;
    return NULL;
}

int web_start_server(const wssshd_config_t *config, wssshd_state_t *state) {
    if (!config->web_host || config->web_port == 0) {
        return 0; // Web interface not configured
    }

    global_config = config;
    global_state = state;

    // Print ASCII art banner
    printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠖⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⢠⠖⠋⠀⠀⠀⣀⣹⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⢸⡄⠀⢠⣶⡞⢁⣸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⠀⠀⠀⠀⠀⠀⠈⢁⣠⠞⢻⡁⢻⣷⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⠀⠀⠀⠀⣠⠤⢄⣘⣿⢬⣛⡷⢋⣈⣉⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⠀⠀⠀⢰⡇⠀⠀⠙⠿⠀⢀⠿⠋⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("⠀⠀⠀⠀⠘⡃⠀⢰⠀⠀⠀⡁⠀⡀⠀⠘⣆⠈⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n");
    printf("\033[38;5;231m⠀⠀⠀⠀⠀⡇⠀⣼⡦⠠⠤⣈⡆⢃⡤⠒⠙⡆⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;231m⠀⠀⠀⠀⢀⠇⠀⡇⢷⣄⣀⡠⠟⠛⠢⠤⠞⡟⠦⡀⠙⠦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;231m⠀⠀⠀⠀⡞⢀⠀⠀⠀⠳⡄⠀⠀⠠⡀⠀⠀⠘⠉⠙⠦⣀⠀⠉⠢⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;231m⠀⠀⠀⠀⡇⠀⢠⠀⠀⠀⠈⠓⢄⠀⠁⠀⠀⠀⠒⠂⠀⣾⠋⠉⠒⠢⢍⣙⡒⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⠀⠀⠀⡇⠀⡜⠀⠀⠀⠀⠀⠀⠱⡀⢀⡀⠁⠀⢀⡼⡇⠀⠀⠀⠀⠀⣏⠙⠯⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⠀⠀⠀⡇⢠⠇⠀⠀⠀⠀⠀⠀⠀⢱⣀⣽⠶⠾⠛⠒⠛⠒⠒⠒⠤⠤⣸⡍⠀⠀⠉⠲⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;218m⠀⣀⣀⣀⠇⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⢫⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣙⠢⣀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;117m⠉⠙⠛⠥⠰⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠂⠤⠤⠤⠤⠴⠒⠚⠉⠙⠢⣀⠀⠈⠑⠢⢄⡀⠈⠳⡀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
    printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠒⠒⠒⠒⠚⠑⠢⢌⡓⠤⠤⠤⠤⣀⠀\033[0m\n");
    printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠒⠠⠤⣼⠇\033[0m\n");
    printf("\n");

    init_users(config);

    // Start HTTP server thread
    pthread_t thread;
    if (pthread_create(&thread, NULL, http_server_thread, NULL) != 0) {
        perror("Failed to create HTTP server thread");
        return -1;
    }

    pthread_detach(thread);
    return 0;
}

void web_stop_server(void) {
    server_running = 0;
    if (server_socket >= 0) {
        close(server_socket);
        server_socket = -1;
    }
    if (db) {
        sqlite3_close(db);
        db = NULL;
    }
    printf("Web interface stopping\n");
}
