// Define _GNU_SOURCE to ensure all POSIX features are available
#define _GNU_SOURCE

// API module for serving embedded web content and admin API functions
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <libwebsockets.h>
#include <cjson/cJSON.h>
#include <sqlite3.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <strings.h>
#include <stdbool.h>
#include "api.h"
#include "embedded_web_content.h"
#include "config.h"
#include "debug.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include "subscriptions.h"

// External subscription manager (from main.c via subscriptions.c)
extern subscription_manager_t g_subscription_manager;

// Global variables for config change system
static pending_config_change_t* pending_changes_head = NULL;
static int pending_changes_count = 0;
#define CONFIG_CHANGE_TIMEOUT 300  // 5 minutes

// Forward declarations for database functions
int store_event(cJSON* event);
int broadcast_event_to_subscriptions(cJSON* event);

// Forward declarations for config functions
char* get_relay_private_key(void);
const char* get_config_value(const char* key);
int get_config_bool(const char* key, int default_value);
int update_config_in_table(const char* key, const char* value);

// Monitoring system state (throttling now handled per-function)

// Forward declaration for monitoring helper function
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));

// Forward declaration for CPU metrics query function
cJSON* query_cpu_metrics(void);

// Monitoring system helper functions
int get_monitoring_throttle_seconds(void) {
    return get_config_int("kind_24567_reporting_throttle_sec", 5);
}

// Query event kind distribution from database
cJSON* query_event_kind_distribution(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for monitoring query");
        return NULL;
    }

    // Query event kinds distribution with total count
    sqlite3_stmt* stmt;
    const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";

    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        DEBUG_ERROR("Failed to prepare event kind distribution query");
        return NULL;
    }

    cJSON* distribution = cJSON_CreateObject();
    cJSON_AddStringToObject(distribution, "data_type", "event_kinds");
    cJSON_AddNumberToObject(distribution, "timestamp", (double)time(NULL));

    cJSON* kinds_array = cJSON_CreateArray();
    long long total_events = 0;

    while (sqlite3_step(stmt) == SQLITE_ROW) {
        int kind = sqlite3_column_int(stmt, 0);
        long long count = sqlite3_column_int64(stmt, 1);
        total_events += count;

        cJSON* kind_obj = cJSON_CreateObject();
        cJSON_AddNumberToObject(kind_obj, "kind", kind);
        cJSON_AddNumberToObject(kind_obj, "count", count);
        cJSON_AddItemToArray(kinds_array, kind_obj);
    }

    sqlite3_finalize(stmt);

    cJSON_AddNumberToObject(distribution, "total_events", total_events);
    cJSON_AddItemToObject(distribution, "kinds", kinds_array);

    return distribution;
}

// Query time-based statistics from database
cJSON* query_time_based_statistics(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for time stats query");
        return NULL;
    }

    time_t now = time(NULL);
    cJSON* time_stats = cJSON_CreateObject();
    cJSON_AddStringToObject(time_stats, "data_type", "time_stats");
    cJSON_AddNumberToObject(time_stats, "timestamp", (double)now);

    cJSON* periods_array = cJSON_CreateArray();

    // Define time periods: 24h, 7d, 30d
    struct {
        const char* period;
        time_t seconds;
        const char* description;
    } periods[] = {
        {"last_24h", 86400, "Events in the last 24 hours"},
        {"last_7d", 604800, "Events in the last 7 days"},
        {"last_30d", 2592000, "Events in the last 30 days"},
        {NULL, 0, NULL}
    };

    // Get total events count
    sqlite3_stmt* total_stmt;
    const char* total_sql = "SELECT COUNT(*) FROM events";
    long long total_events = 0;

    if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(total_stmt) == SQLITE_ROW) {
            total_events = sqlite3_column_int64(total_stmt, 0);
        }
        sqlite3_finalize(total_stmt);
    }

    // Query each time period
    for (int i = 0; periods[i].period != NULL; i++) {
        sqlite3_stmt* stmt;
        const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";

        if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
            DEBUG_ERROR("Failed to prepare time stats query");
            continue;
        }

        time_t cutoff = now - periods[i].seconds;
        sqlite3_bind_int64(stmt, 1, cutoff);

        long long count = 0;
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            count = sqlite3_column_int64(stmt, 0);
        }

        sqlite3_finalize(stmt);

        cJSON* period_obj = cJSON_CreateObject();
        cJSON_AddStringToObject(period_obj, "period", periods[i].period);
        cJSON_AddNumberToObject(period_obj, "count", count);
        cJSON_AddStringToObject(period_obj, "description", periods[i].description);
        cJSON_AddItemToArray(periods_array, period_obj);
    }

    cJSON_AddItemToObject(time_stats, "periods", periods_array);
    cJSON_AddNumberToObject(time_stats, "total_events", total_events);

    return time_stats;
}

// Query top pubkeys by event count from database
cJSON* query_top_pubkeys(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for top pubkeys query");
        return NULL;
    }

    // Query top 10 pubkeys by event count
    sqlite3_stmt* stmt;
    const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT 10";

    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        DEBUG_ERROR("Failed to prepare top pubkeys query");
        return NULL;
    }

    cJSON* top_pubkeys = cJSON_CreateObject();
    cJSON_AddStringToObject(top_pubkeys, "data_type", "top_pubkeys");
    cJSON_AddNumberToObject(top_pubkeys, "timestamp", (double)time(NULL));

    cJSON* pubkeys_array = cJSON_CreateArray();

    // Get total events count for percentage calculation
    sqlite3_stmt* total_stmt;
    const char* total_sql = "SELECT COUNT(*) FROM events";
    long long total_events = 0;

    if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(total_stmt) == SQLITE_ROW) {
            total_events = sqlite3_column_int64(total_stmt, 0);
        }
        sqlite3_finalize(total_stmt);
    }

    while (sqlite3_step(stmt) == SQLITE_ROW) {
        const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
        long long count = sqlite3_column_int64(stmt, 1);

        cJSON* pubkey_obj = cJSON_CreateObject();
        cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
        cJSON_AddNumberToObject(pubkey_obj, "event_count", count);
        // Percentage will be calculated by frontend using total_events
        cJSON_AddItemToArray(pubkeys_array, pubkey_obj);
    }

    sqlite3_finalize(stmt);

    cJSON_AddItemToObject(top_pubkeys, "pubkeys", pubkeys_array);
    cJSON_AddNumberToObject(top_pubkeys, "total_events", total_events);

    return top_pubkeys;
}


// Query detailed subscription information from database log (ADMIN ONLY)
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
cJSON* query_subscription_details(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for subscription details query");
        return NULL;
    }

    // Query active subscriptions from the active_subscriptions_log view
    // This view properly handles deduplication of closed/expired subscriptions
    sqlite3_stmt* stmt;
    const char* sql =
        "SELECT * "
        "FROM active_subscriptions_log "
        "ORDER BY created_at DESC";

    // DEBUG: Log the query results for debugging subscription_details
    DEBUG_LOG("=== SUBSCRIPTION_DETAILS QUERY DEBUG ===");
    DEBUG_LOG("Query: %s", sql);

    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        DEBUG_ERROR("Failed to prepare subscription details query");
        return NULL;
    }

    time_t current_time = time(NULL);
    cJSON* subscriptions_data = cJSON_CreateObject();
    cJSON_AddStringToObject(subscriptions_data, "data_type", "subscription_details");
    cJSON_AddNumberToObject(subscriptions_data, "timestamp", (double)current_time);

    cJSON* data = cJSON_CreateObject();
    cJSON* subscriptions_array = cJSON_CreateArray();

    // Iterate through query results
    int row_count = 0;
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        row_count++;
        cJSON* sub_obj = cJSON_CreateObject();

        // Extract subscription data from database
        const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
        const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
        const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
        long long events_sent = sqlite3_column_int64(stmt, 3);
        long long created_at = sqlite3_column_int64(stmt, 4);
        long long duration_seconds = sqlite3_column_int64(stmt, 5);
        const char* wsi_pointer = (const char*)sqlite3_column_text(stmt, 6);

        // DEBUG: Log each subscription found
        DEBUG_LOG("Row %d: sub_id=%s, client_ip=%s, events_sent=%lld, created_at=%lld",
                  row_count, sub_id ? sub_id : "NULL", client_ip ? client_ip : "NULL",
                  events_sent, created_at);

        // Add basic subscription info
        cJSON_AddStringToObject(sub_obj, "id", sub_id ? sub_id : "");
        cJSON_AddStringToObject(sub_obj, "client_ip", client_ip ? client_ip : "");
        cJSON_AddNumberToObject(sub_obj, "created_at", (double)created_at);
        cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)duration_seconds);
        cJSON_AddNumberToObject(sub_obj, "events_sent", events_sent);
        cJSON_AddBoolToObject(sub_obj, "active", 1);  // All from this view are active
        cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer ? wsi_pointer : "N/A");

        // Parse and add filter JSON if available
        if (filter_json) {
            cJSON* filters = cJSON_Parse(filter_json);
            if (filters) {
                cJSON_AddItemToObject(sub_obj, "filters", filters);
            } else {
                // If parsing fails, add empty array
                cJSON_AddItemToObject(sub_obj, "filters", cJSON_CreateArray());
            }
        } else {
            cJSON_AddItemToObject(sub_obj, "filters", cJSON_CreateArray());
        }

        cJSON_AddItemToArray(subscriptions_array, sub_obj);
    }

    sqlite3_finalize(stmt);

    // Add subscriptions array and count to data
    cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
    cJSON_AddNumberToObject(data, "total_count", cJSON_GetArraySize(subscriptions_array));

    cJSON_AddItemToObject(subscriptions_data, "data", data);

    // DEBUG: Log final summary
    DEBUG_LOG("Total subscriptions found: %d", cJSON_GetArraySize(subscriptions_array));
    DEBUG_LOG("=== END SUBSCRIPTION_DETAILS QUERY DEBUG ===");

    return subscriptions_data;
}

// Generate event-driven monitoring events (triggered by event storage)
int generate_event_driven_monitoring(void) {
    // Generate event_kinds monitoring event
    if (generate_monitoring_event_for_type("event_kinds", query_event_kind_distribution) != 0) {
        DEBUG_ERROR("Failed to generate event_kinds monitoring event");
        return -1;
    }

    // Generate time_stats monitoring event
    if (generate_monitoring_event_for_type("time_stats", query_time_based_statistics) != 0) {
        DEBUG_ERROR("Failed to generate time_stats monitoring event");
        return -1;
    }

    // Generate top_pubkeys monitoring event
    if (generate_monitoring_event_for_type("top_pubkeys", query_top_pubkeys) != 0) {
        DEBUG_ERROR("Failed to generate top_pubkeys monitoring event");
        return -1;
    }


    // Generate CPU metrics monitoring event (also triggered by event storage)
    if (generate_monitoring_event_for_type("cpu_metrics", query_cpu_metrics) != 0) {
        DEBUG_ERROR("Failed to generate cpu_metrics monitoring event");
        return -1;
    }

    return 0;
}

// Generate subscription-driven monitoring events (triggered by subscription changes)
int generate_subscription_driven_monitoring(void) {

    // Generate subscription_details monitoring event (admin-only)
    if (generate_monitoring_event_for_type("subscription_details", query_subscription_details) != 0) {
        DEBUG_ERROR("Failed to generate subscription_details monitoring event");
        return -1;
    }

    // Generate CPU metrics monitoring event (also triggered by subscription changes)
    if (generate_monitoring_event_for_type("cpu_metrics", query_cpu_metrics) != 0) {
        DEBUG_ERROR("Failed to generate cpu_metrics monitoring event");
        return -1;
    }

    return 0;
}

// Generate and broadcast monitoring event (legacy function - now calls event-driven version)
int generate_monitoring_event(void) {
    return generate_event_driven_monitoring();
}

// Helper function to generate monitoring event for a specific type
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void)) {
    // Query the monitoring data
    cJSON* monitoring_data = query_func();
    if (!monitoring_data) {
        DEBUG_ERROR("Failed to query monitoring data for %s", d_tag_value);
        return -1;
    }

    // Convert to JSON string for content
    char* content_json = cJSON_Print(monitoring_data);
    cJSON_Delete(monitoring_data);

    if (!content_json) {
        DEBUG_ERROR("Failed to serialize monitoring data for %s", d_tag_value);
        return -1;
    }

    // Get relay keys for signing
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        free(content_json);
        DEBUG_ERROR("Could not get relay keys for monitoring event (%s)", d_tag_value);
        return -1;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        free(content_json);
        DEBUG_ERROR("Failed to convert relay private key for monitoring event (%s)", d_tag_value);
        return -1;
    }
    free(relay_privkey_hex);

    // Create monitoring event (kind 24567 - ephemeral)
    cJSON* monitoring_event = cJSON_CreateObject();
    cJSON_AddStringToObject(monitoring_event, "id", ""); // Will be set by signing
    cJSON_AddStringToObject(monitoring_event, "pubkey", relay_pubkey);
    cJSON_AddNumberToObject(monitoring_event, "created_at", (double)time(NULL));
    cJSON_AddNumberToObject(monitoring_event, "kind", 24567);
    cJSON_AddStringToObject(monitoring_event, "content", content_json);

    // Create tags array with d tag for identification
    cJSON* tags = cJSON_CreateArray();

    // d tag for event identification
    cJSON* d_tag = cJSON_CreateArray();
    cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
    cJSON_AddItemToArray(d_tag, cJSON_CreateString(d_tag_value));
    cJSON_AddItemToArray(tags, d_tag);

    cJSON_AddItemToObject(monitoring_event, "tags", tags);

    // Use the library function to create and sign the event
    cJSON* signed_event = nostr_create_and_sign_event(
        24567,                              // kind (ephemeral)
        cJSON_GetStringValue(cJSON_GetObjectItem(monitoring_event, "content")), // content
        tags,                               // tags
        relay_privkey,                      // private key
        (time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(monitoring_event, "created_at")) // timestamp
    );

    if (!signed_event) {
        cJSON_Delete(monitoring_event);
        free(content_json);
        DEBUG_ERROR("Failed to create and sign monitoring event (%s)", d_tag_value);
        return -1;
    }

    // Replace the unsigned event with the signed one
    cJSON_Delete(monitoring_event);
    monitoring_event = signed_event;

    // Broadcast the ephemeral event to active subscriptions (no database storage)
    broadcast_event_to_subscriptions(monitoring_event);

    cJSON_Delete(monitoring_event);
    free(content_json);

    DEBUG_LOG("Monitoring event broadcast (ephemeral kind 24567, type: %s)", d_tag_value);
    return 0;
}

// Monitoring hook called when an event is stored
void monitoring_on_event_stored(void) {
    // Check throttling first (cheapest check)
    static time_t last_monitoring_time = 0;
    time_t current_time = time(NULL);
    int throttle_seconds = get_monitoring_throttle_seconds();

    if (current_time - last_monitoring_time < throttle_seconds) {
        return;
    }

    // Check if anyone is subscribed to monitoring events (kind 24567)
    // This is the ONLY activation check needed - if someone subscribes, they want monitoring
    if (!has_subscriptions_for_kind(24567)) {
        return; // No subscribers = no expensive operations
    }

    // Generate event-driven monitoring events only when someone is listening
    last_monitoring_time = current_time;
    generate_event_driven_monitoring();
}

// Monitoring hook called when subscriptions change (create/close)
void monitoring_on_subscription_change(void) {
    // Check throttling first (cheapest check)
    static time_t last_monitoring_time = 0;
    time_t current_time = time(NULL);
    int throttle_seconds = get_monitoring_throttle_seconds();

    if (current_time - last_monitoring_time < throttle_seconds) {
        return;
    }

    // Check if anyone is subscribed to monitoring events (kind 24567)
    // This is the ONLY activation check needed - if someone subscribes, they want monitoring
    if (!has_subscriptions_for_kind(24567)) {
        return; // No subscribers = no expensive operations
    }

    // Generate subscription-driven monitoring events only when someone is listening
    last_monitoring_time = current_time;
    generate_subscription_driven_monitoring();
}

/**
 * Generate and post a kind 1 status event with relay statistics
 * Called periodically from main event loop based on configuration
 * Returns: 0 on success, -1 on error
 */
int generate_and_post_status_event(void) {
    // Check if feature is enabled
    int hours_interval = get_config_int("kind_1_status_posts_hours", 0);
    if (hours_interval <= 0) {
        return 0; // Feature disabled
    }

    // Generate statistics text content using existing function
    char* stats_text = generate_stats_text();
    if (!stats_text) {
        DEBUG_ERROR("Failed to generate statistics text for status post");
        return -1;
    }

    // Get relay private key
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_privkey_hex) {
        DEBUG_ERROR("Failed to get relay private key for status post");
        free(stats_text);
        return -1;
    }

    // Convert hex private key to bytes
    unsigned char relay_privkey_bytes[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey_bytes, 32) != 0) {
        DEBUG_ERROR("Failed to convert relay private key to bytes");
        free(stats_text);
        return -1;
    }

    // Create empty tags array (kind 1 doesn't require special tags)
    cJSON* tags = cJSON_CreateArray();

    // Create and sign the kind 1 event
    cJSON* signed_event = nostr_create_and_sign_event(
        1,                      // kind 1 = text note
        stats_text,             // content = statistics
        tags,                   // empty tags
        relay_privkey_bytes,    // relay's private key
        time(NULL)              // current timestamp
    );

    free(stats_text);

    if (!signed_event) {
        DEBUG_ERROR("Failed to create and sign status event");
        return -1;
    }

    // Store event in database (kind 1 events MUST be stored)
    if (store_event(signed_event) != 0) {
        DEBUG_ERROR("Failed to store status event in database");
        cJSON_Delete(signed_event);
        return -1;
    }

    // Broadcast event to subscriptions
    broadcast_event_to_subscriptions(signed_event);

    DEBUG_LOG("Successfully posted kind 1 status event");
    cJSON_Delete(signed_event);

    return 0;
}

// Forward declaration for known_configs (defined in config.c)
typedef struct {
    const char* key;
    const char* type;
    int min_val;
    int max_val;
} config_definition_t;

// Define known_configs array locally in api.c
static const config_definition_t known_configs[] = {
    // Authentication
    {"auth_enabled", "bool", 0, 1},
    {"nip42_auth_required", "bool", 0, 1},
    {"nip42_auth_required_events", "bool", 0, 1},
    {"nip42_auth_required_subscriptions", "bool", 0, 1},
    {"nip42_auth_required_kinds", "string", 0, 255},
    {"nip42_challenge_expiration", "int", 60, 3600},
    {"nip42_challenge_timeout", "int", 60, 3600},
    {"nip42_time_tolerance", "int", 60, 3600},
    {"nip70_protected_events_enabled", "bool", 0, 1},

    // Server Core Settings
    {"relay_port", "int", 1, 65535},
    {"max_connections", "int", 1, 10000},

    // NIP-11 Relay Information
    {"relay_name", "string", 0, 256},
    {"relay_description", "string", 0, 512},
    {"relay_contact", "string", 0, 256},
    {"relay_software", "string", 0, 256},
    {"relay_version", "string", 0, 256},
    {"supported_nips", "string", 0, 1024},
    {"language_tags", "string", 0, 1024},
    {"relay_countries", "string", 0, 1024},
    {"posting_policy", "string", 0, 1024},
    {"payments_url", "string", 0, 1024},

    // NIP-13 Proof of Work
    {"pow_min_difficulty", "int", 0, 32},
    {"pow_mode", "string", 0, 32},

    // NIP-40 Expiration Timestamp
    {"nip40_expiration_enabled", "bool", 0, 1},
    {"nip40_expiration_strict", "bool", 0, 1},
    {"nip40_expiration_filter", "bool", 0, 1},
    {"nip40_expiration_grace_period", "int", 0, 86400},

    // Subscription Limits
    {"max_subscriptions_per_client", "int", 1, 1000},
    {"max_total_subscriptions", "int", 1, 100000},
    {"max_filters_per_subscription", "int", 1, 100},

    // Event Processing Limits
    {"max_event_tags", "int", 1, 1000},
    {"max_content_length", "int", 100, 1048576},
    {"max_message_length", "int", 1024, 1048576},

    // Performance Settings
    {"default_limit", "int", 1, 50000},
    {"max_limit", "int", 1, 50000},

    // Relay keys
    {"relay_pubkey", "string", 0, 65},
    {"relay_privkey", "string", 0, 65},
    {"admin_pubkey", "string", 0, 65},

    // Kind 1 Status Posts
    {"kind_1_status_posts_hours", "int", 0, 8760},

    // Sentinel
    {NULL, NULL, 0, 0}
};
// External database connection (from main.c)
extern sqlite3* g_db;
extern char g_database_path[512];

// Forward declarations for database functions
int store_event(cJSON* event);
int broadcast_event_to_subscriptions(cJSON* event);

// Forward declarations for config functions
char* get_relay_private_key(void);
const char* get_config_value(const char* key);
int get_config_bool(const char* key, int default_value);
int update_config_in_table(const char* key, const char* value);

// Forward declaration for monitoring helper function
int generate_monitoring_event_for_type(const char* d_tag_value, cJSON* (*query_func)(void));

// Handle HTTP request for embedded files (assumes GET)
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
    const char* file_path;

    // Handle /api requests
    char temp_path[256];
    if (strcmp(requested_uri, "/api") == 0) {
        // /api -> serve index.html
        file_path = "/";
    } else if (strncmp(requested_uri, "/api/", 5) == 0) {
        // Extract file path from /api/ prefix and add leading slash for lookup
        snprintf(temp_path, sizeof(temp_path), "/%s", requested_uri + 5); // Add leading slash
        file_path = temp_path;
    } else {
        DEBUG_WARN("Embedded file request without /api prefix");
        lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
        return -1;
    }

    // Get embedded file
    embedded_file_t* file = get_embedded_file(file_path);
    if (!file) {
        DEBUG_WARN("Embedded file not found");
        lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
        return -1;
    }

    // Allocate session data
    struct embedded_file_session_data* session_data = malloc(sizeof(struct embedded_file_session_data));
    if (!session_data) {
        DEBUG_ERROR("Failed to allocate embedded file session data");
        return -1;
    }

    session_data->type = 1; // Embedded file
    session_data->data = file->data;
    session_data->size = file->size;
    session_data->content_type = file->content_type;
    session_data->headers_sent = 0;
    session_data->body_sent = 0;

    // Store session data
    lws_set_wsi_user(wsi, session_data);

    // Prepare HTTP response headers
    unsigned char buf[LWS_PRE + 1024];
    unsigned char *p = &buf[LWS_PRE];
    unsigned char *start = p;
    unsigned char *end = &buf[sizeof(buf) - 1];

    if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) {
        free(session_data);
        return -1;
    }

    if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)file->content_type, strlen(file->content_type), &p, end)) {
        free(session_data);
        return -1;
    }

    if (lws_add_http_header_content_length(wsi, file->size, &p, end)) {
        free(session_data);
        return -1;
    }

    // Add CORS headers (same as NIP-11 for consistency)
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-origin:", (unsigned char*)"*", 1, &p, end)) {
        free(session_data);
        return -1;
    }
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-headers:", (unsigned char*)"content-type, accept", 20, &p, end)) {
        free(session_data);
        return -1;
    }
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-methods:", (unsigned char*)"GET, OPTIONS", 12, &p, end)) {
        free(session_data);
        return -1;
    }

    // Add Connection: close to ensure connection closes after response
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"connection:", (unsigned char*)"close", 5, &p, end)) {
        free(session_data);
        return -1;
    }

    if (lws_finalize_http_header(wsi, &p, end)) {
        free(session_data);
        return -1;
    }

    // Write headers
    if (lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS) < 0) {
        free(session_data);
        return -1;
    }

    session_data->headers_sent = 1;

    // Request callback for body transmission
    lws_callback_on_writable(wsi);

    return 0;
}

// Send admin response with request ID correlation
int send_admin_response(const char* sender_pubkey, const char* response_content, const char* request_id,
                        char* error_message, size_t error_size, struct lws* wsi) {
    (void)wsi; // Suppress unused parameter warning
    if (!sender_pubkey || !response_content || !request_id || !error_message) {
        if (error_message) {
            strncpy(error_message, "Admin response: Invalid parameters", error_size - 1);
        }
        return -1;
    }

    // Get relay keys for signing
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        if (relay_privkey_hex) free(relay_privkey_hex);
        strncpy(error_message, "Admin response: Could not get relay keys", error_size - 1);
        return -1;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        strncpy(error_message, "Admin response: Failed to convert relay private key", error_size - 1);
        return -1;
    }
    free(relay_privkey_hex);

    // Convert sender pubkey to bytes for NIP-44 encryption
    unsigned char sender_pubkey_bytes[32];
    if (nostr_hex_to_bytes(sender_pubkey, sender_pubkey_bytes, sizeof(sender_pubkey_bytes)) != 0) {
        strncpy(error_message, "Admin response: Failed to convert sender pubkey", error_size - 1);
        return -1;
    }

    // Encrypt response content using NIP-44
    char encrypted_content[131072]; // Buffer for encrypted content (128KB to handle large SQL responses)
    int encrypt_result = nostr_nip44_encrypt(
        relay_privkey,             // sender private key (bytes)
        sender_pubkey_bytes,       // recipient public key (bytes)
        response_content,          // plaintext
        encrypted_content,         // output buffer
        sizeof(encrypted_content)  // output buffer size
    );

    if (encrypt_result != 0) {
        strncpy(error_message, "Admin response: Failed to encrypt response content", error_size - 1);
        return -1;
    }

    // Create response event (kind 23457)
    cJSON* response_event = cJSON_CreateObject();
    cJSON_AddStringToObject(response_event, "id", ""); // Will be set by signing
    cJSON_AddStringToObject(response_event, "pubkey", relay_pubkey);
    cJSON_AddNumberToObject(response_event, "created_at", (double)time(NULL));
    cJSON_AddNumberToObject(response_event, "kind", 23457);
    cJSON_AddStringToObject(response_event, "content", encrypted_content);

    // Create tags array with p tag and e tag for request correlation
    cJSON* tags = cJSON_CreateArray();

    // p tag for recipient
    cJSON* p_tag = cJSON_CreateArray();
    cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
    cJSON_AddItemToArray(p_tag, cJSON_CreateString(sender_pubkey));
    cJSON_AddItemToArray(tags, p_tag);

    // e tag for request event correlation
    cJSON* e_tag = cJSON_CreateArray();
    cJSON_AddItemToArray(e_tag, cJSON_CreateString("e"));
    cJSON_AddItemToArray(e_tag, cJSON_CreateString(request_id));
    cJSON_AddItemToArray(tags, e_tag);

    cJSON_AddItemToObject(response_event, "tags", tags);

    // Use the library function to create and sign the event
    cJSON* signed_event = nostr_create_and_sign_event(
        23457,                              // kind
        cJSON_GetStringValue(cJSON_GetObjectItem(response_event, "content")), // content
        tags,                               // tags
        relay_privkey,                      // private key
        (time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(response_event, "created_at")) // timestamp
    );

    if (!signed_event) {
        cJSON_Delete(response_event);
        strncpy(error_message, "Admin response: Failed to create and sign event", error_size - 1);
        return -1;
    }

    // Replace the unsigned event with the signed one
    cJSON_Delete(response_event);
    response_event = signed_event;

    // Broadcast the event
    broadcast_event_to_subscriptions(response_event);

    // Store in database
    int store_result = store_event(response_event);

    cJSON_Delete(response_event);

    if (store_result != 0) {
        strncpy(error_message, "Admin response: Failed to store response event", error_size - 1);
        return -1;
    }

    return 0;
}

// =============================================================================
// SQL QUERY ADMIN FUNCTIONS
// =============================================================================

// Validate SQL query for security and safety
int validate_sql_query(const char* query, char* error_message, size_t error_size) {
    if (!query || !error_message) {
        return 0;
    }

    // Convert query to uppercase for case-insensitive keyword checking
    char query_upper[4096];
    size_t query_len = strlen(query);
    if (query_len >= sizeof(query_upper)) {
        snprintf(error_message, error_size, "Query too long (max %zu characters)", sizeof(query_upper) - 1);
        return 0;
    }

    // Copy and convert to uppercase
    for (size_t i = 0; i < query_len; i++) {
        query_upper[i] = (query[i] >= 'a' && query[i] <= 'z') ? query[i] - 32 : query[i];
    }
    query_upper[query_len] = '\0';

    // List of blocked keywords (case-insensitive)
    const char* blocked_keywords[] = {
        "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
        "EXEC", "EXECUTE", "MERGE", "BULK", "BACKUP", "RESTORE",
        "GRANT", "REVOKE", "DENY", "COMMIT", "ROLLBACK", "SAVEPOINT",
        "SHUTDOWN", "PRAGMA", "VACUUM", "REINDEX", "ANALYZE",
        NULL // Sentinel
    };

    // Check for blocked keywords
    for (int i = 0; blocked_keywords[i] != NULL; i++) {
        char keyword_pattern[64];
        snprintf(keyword_pattern, sizeof(keyword_pattern), " %s ", blocked_keywords[i]);

        if (strstr(query_upper, keyword_pattern) != NULL) {
            snprintf(error_message, error_size, "Query blocked: %s statements not allowed", blocked_keywords[i]);
            return 0;
        }

        // Also check at start of query
        if (strncmp(query_upper, blocked_keywords[i], strlen(blocked_keywords[i])) == 0) {
            snprintf(error_message, error_size, "Query blocked: %s statements not allowed", blocked_keywords[i]);
            return 0;
        }
    }

    // Check for SELECT keyword (must be present)
    if (strstr(query_upper, " SELECT ") == NULL && strncmp(query_upper, "SELECT ", 7) != 0) {
        // Allow WITH clauses (CTEs) which can start queries
        if (strstr(query_upper, " WITH ") == NULL && strncmp(query_upper, "WITH ", 5) != 0) {
            snprintf(error_message, error_size, "Query blocked: Only SELECT statements and WITH clauses are allowed");
            return 0;
        }
    }

    // Basic length check
    if (query_len < 6) { // Minimum "SELECT" length
        snprintf(error_message, error_size, "Query too short");
        return 0;
    }

    return 1; // Query passed validation
}

// Execute SQL query with safety limits
char* execute_sql_query(const char* query, const char* request_id, char* error_message, size_t error_size) {
    if (!query || !request_id || !error_message) {
        return NULL;
    }

    if (!g_db) {
        snprintf(error_message, error_size, "Database not available");
        return NULL;
    }

    // Set busy timeout to prevent long-running queries (5 seconds)
    sqlite3_busy_timeout(g_db, 5000);

    // Prepare statement
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, query, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        const char* err_msg = sqlite3_errmsg(g_db);
        snprintf(error_message, error_size, "SQL prepare failed: %s", err_msg);
        return NULL;
    }

    // Execute query and collect results
    cJSON* response = cJSON_CreateObject();
    cJSON_AddStringToObject(response, "query_type", "sql_query");
    cJSON_AddStringToObject(response, "request_id", request_id);
    cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
    cJSON_AddStringToObject(response, "query", query);

    // Get column information
    int col_count = sqlite3_column_count(stmt);
    cJSON* columns = cJSON_CreateArray();
    for (int i = 0; i < col_count; i++) {
        const char* col_name = sqlite3_column_name(stmt, i);
        cJSON_AddItemToArray(columns, cJSON_CreateString(col_name ? col_name : ""));
    }
    cJSON_AddItemToObject(response, "columns", columns);

    // Execute and collect rows (with limit)
    cJSON* rows = cJSON_CreateArray();
    int row_count = 0;
    const int MAX_ROWS = 1000; // Configurable limit

    struct timespec start_time;
    clock_gettime(CLOCK_MONOTONIC, &start_time);

    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW && row_count < MAX_ROWS) {
        cJSON* row = cJSON_CreateArray();

        for (int i = 0; i < col_count; i++) {
            int col_type = sqlite3_column_type(stmt, i);

            switch (col_type) {
                case SQLITE_INTEGER:
                    cJSON_AddItemToArray(row, cJSON_CreateNumber((double)sqlite3_column_int64(stmt, i)));
                    break;
                case SQLITE_FLOAT:
                    cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_double(stmt, i)));
                    break;
                case SQLITE_TEXT: {
                    const char* text = (const char*)sqlite3_column_text(stmt, i);
                    cJSON_AddItemToArray(row, cJSON_CreateString(text ? text : ""));
                    break;
                }
                case SQLITE_BLOB: {
                    // Convert blob to hex string for JSON compatibility
                    const void* blob = sqlite3_column_blob(stmt, i);
                    int blob_size = sqlite3_column_bytes(stmt, i);
                    if (blob && blob_size > 0) {
                        char* hex_str = malloc(blob_size * 2 + 1);
                        if (hex_str) {
                            for (int j = 0; j < blob_size; j++) {
                                sprintf(hex_str + j * 2, "%02x", ((unsigned char*)blob)[j]);
                            }
                            hex_str[blob_size * 2] = '\0';
                            cJSON_AddItemToArray(row, cJSON_CreateString(hex_str));
                            free(hex_str);
                        } else {
                            cJSON_AddItemToArray(row, cJSON_CreateString("[BLOB]"));
                        }
                    } else {
                        cJSON_AddItemToArray(row, cJSON_CreateString(""));
                    }
                    break;
                }
                case SQLITE_NULL:
                    cJSON_AddItemToArray(row, cJSON_CreateNull());
                    break;
                default:
                    cJSON_AddItemToArray(row, cJSON_CreateString("[UNKNOWN]"));
                    break;
            }
        }

        cJSON_AddItemToArray(rows, row);
        row_count++;

        // Check timeout (additional safety check)
        struct timespec current_time;
        clock_gettime(CLOCK_MONOTONIC, &current_time);
        double elapsed = (current_time.tv_sec - start_time.tv_sec) +
                        (current_time.tv_nsec - start_time.tv_nsec) / 1e9;
        if (elapsed > 4.5) { // 4.5 seconds to allow for cleanup
            break;
        }
    }

    sqlite3_finalize(stmt);

    // Check for execution errors
    if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
        const char* err_msg = sqlite3_errmsg(g_db);
        snprintf(error_message, error_size, "SQL execution failed: %s", err_msg);
        cJSON_Delete(response);
        return NULL;
    }

    // Check row limit
    if (row_count >= MAX_ROWS) {
        cJSON_AddStringToObject(response, "warning", "Result truncated to maximum row limit");
    }

    // Add metadata
    cJSON_AddNumberToObject(response, "row_count", row_count);
    cJSON_AddNumberToObject(response, "execution_time_ms", 0); // Will be set by caller
    cJSON_AddItemToObject(response, "rows", rows);

    // Convert to JSON string
    char* json_result = cJSON_Print(response);
    cJSON_Delete(response);

    if (!json_result) {
        snprintf(error_message, error_size, "Failed to generate JSON response");
        return NULL;
    }

    return json_result;
}

// Unified handler for SQL query commands
int handle_sql_query_unified(cJSON* event, const char* query, char* error_message, size_t error_size, struct lws* wsi) {
    if (!event || !query || !error_message) {
        return -1;
    }

    // Get request event ID for response correlation
    cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
    if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
        snprintf(error_message, error_size, "Missing request event ID");
        return -1;
    }
    const char* request_id = cJSON_GetStringValue(request_id_obj);

    // Validate query
    if (!validate_sql_query(query, error_message, error_size)) {
        return -1;
    }

    // Execute query
    char* result_json = execute_sql_query(query, request_id, error_message, error_size);
    if (!result_json) {
        return -1;
    }

    // Get sender pubkey for response
    cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
        free(result_json);
        snprintf(error_message, error_size, "Missing sender pubkey");
        return -1;
    }
    const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

    // Send response as kind 23457 event with request ID in tags
    int send_result = send_admin_response(sender_pubkey, result_json, request_id, error_message, error_size, wsi);
    free(result_json);

    return send_result;
}

// Handle HTTP_WRITEABLE for embedded files
int handle_embedded_file_writeable(struct lws* wsi) {
    struct embedded_file_session_data* session_data = (struct embedded_file_session_data*)lws_wsi_user(wsi);
    if (!session_data || session_data->headers_sent == 0 || session_data->body_sent == 1) {
        return 0;
    }

    // Allocate buffer for data transmission
    unsigned char *buf = malloc(LWS_PRE + session_data->size);
    if (!buf) {
        DEBUG_ERROR("Failed to allocate buffer for embedded file transmission");
        free(session_data);
        lws_set_wsi_user(wsi, NULL);
        return -1;
    }

    // Copy data to buffer
    memcpy(buf + LWS_PRE, session_data->data, session_data->size);

    // Write data
    int write_result = lws_write(wsi, buf + LWS_PRE, session_data->size, LWS_WRITE_HTTP);

    // Free the transmission buffer
    free(buf);

    if (write_result < 0) {
        DEBUG_ERROR("Failed to write embedded file data");
        free(session_data);
        lws_set_wsi_user(wsi, NULL);
        return -1;
    }

    // Mark as sent and clean up
    session_data->body_sent = 1;
    free(session_data);
    lws_set_wsi_user(wsi, NULL);

    return 0;
}
// Query CPU usage metrics
cJSON* query_cpu_metrics(void) {
    cJSON* cpu_stats = cJSON_CreateObject();
    cJSON_AddStringToObject(cpu_stats, "data_type", "cpu_metrics");
    cJSON_AddNumberToObject(cpu_stats, "timestamp", (double)time(NULL));

    // Read process CPU times from /proc/self/stat
    FILE* proc_stat = fopen("/proc/self/stat", "r");
    if (proc_stat) {
        unsigned long utime, stime; // user and system CPU time in clock ticks
        if (fscanf(proc_stat, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &utime, &stime) == 2) {
            unsigned long total_proc_time = utime + stime;

            // Get system CPU times from /proc/stat
            FILE* sys_stat = fopen("/proc/stat", "r");
            if (sys_stat) {
                unsigned long user, nice, system, idle, iowait, irq, softirq;
                if (fscanf(sys_stat, "cpu %lu %lu %lu %lu %lu %lu %lu", &user, &nice, &system, &idle, &iowait, &irq, &softirq) == 7) {
                    unsigned long total_sys_time = user + nice + system + idle + iowait + irq + softirq;

                    // Calculate CPU percentages (simplified - would need deltas for accuracy)
                    // For now, just store the raw values - frontend can calculate deltas
                    cJSON_AddNumberToObject(cpu_stats, "process_cpu_time", (double)total_proc_time);
                    cJSON_AddNumberToObject(cpu_stats, "system_cpu_time", (double)total_sys_time);
                    cJSON_AddNumberToObject(cpu_stats, "system_idle_time", (double)idle);
                }
                fclose(sys_stat);
            }

            // Get current CPU core the process is running on
            int current_core = sched_getcpu();
            if (current_core >= 0) {
                cJSON_AddNumberToObject(cpu_stats, "current_cpu_core", current_core);
            }
        }
        fclose(proc_stat);
    }

    // Get process ID
    pid_t pid = getpid();
    cJSON_AddNumberToObject(cpu_stats, "process_id", (double)pid);

    // Get memory usage from /proc/self/status
    FILE* mem_stat = fopen("/proc/self/status", "r");
    if (mem_stat) {
        char line[256];
        while (fgets(line, sizeof(line), mem_stat)) {
            if (strncmp(line, "VmRSS:", 6) == 0) {
                unsigned long rss_kb;
                if (sscanf(line, "VmRSS: %lu kB", &rss_kb) == 1) {
                    double rss_mb = rss_kb / 1024.0;
                    cJSON_AddNumberToObject(cpu_stats, "memory_usage_mb", rss_mb);
                }
                break;
            }
        }
        fclose(mem_stat);
    }

    return cpu_stats;
}

// Generate stats JSON from database queries
char* generate_stats_json(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for stats generation");
        return NULL;
    }

    // Build response with database statistics
    cJSON* response = cJSON_CreateObject();
    cJSON_AddStringToObject(response, "query_type", "stats_query");
    cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));

    // Get database file size
    extern char g_database_path[512];
    struct stat db_stat;
    long long db_size = 0;
    if (stat(g_database_path, &db_stat) == 0) {
        db_size = db_stat.st_size;
    }
    cJSON_AddNumberToObject(response, "database_size_bytes", db_size);

    // Get active subscriptions count from in-memory manager
    pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
    int active_subs = g_subscription_manager.total_subscriptions;
    pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
    cJSON_AddNumberToObject(response, "active_subscriptions", active_subs);

    // Query total events count
    sqlite3_stmt* stmt;
    if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            cJSON_AddNumberToObject(response, "total_events", sqlite3_column_int64(stmt, 0));
        }
        sqlite3_finalize(stmt);
    }

    // Query event kinds distribution
    cJSON* event_kinds = cJSON_CreateArray();
    if (sqlite3_prepare_v2(g_db, "SELECT kind, count, percentage FROM event_kinds_view ORDER BY count DESC", -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            cJSON* kind_obj = cJSON_CreateObject();
            cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0));
            cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1));
            cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2));
            cJSON_AddItemToArray(event_kinds, kind_obj);
        }
        sqlite3_finalize(stmt);
    }
    cJSON_AddItemToObject(response, "event_kinds", event_kinds);

    // Query time-based statistics
    cJSON* time_stats = cJSON_CreateObject();
    if (sqlite3_prepare_v2(g_db, "SELECT period, total_events FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const char* period = (const char*)sqlite3_column_text(stmt, 0);
            sqlite3_int64 count = sqlite3_column_int64(stmt, 1);

            if (strcmp(period, "total") == 0) {
                cJSON_AddNumberToObject(time_stats, "total", count);
            } else if (strcmp(period, "24h") == 0) {
                cJSON_AddNumberToObject(time_stats, "last_24h", count);
            } else if (strcmp(period, "7d") == 0) {
                cJSON_AddNumberToObject(time_stats, "last_7d", count);
            } else if (strcmp(period, "30d") == 0) {
                cJSON_AddNumberToObject(time_stats, "last_30d", count);
            }
        }
        sqlite3_finalize(stmt);
    }
    cJSON_AddItemToObject(response, "time_stats", time_stats);

    // Query top pubkeys
    cJSON* top_pubkeys = cJSON_CreateArray();
    if (sqlite3_prepare_v2(g_db, "SELECT pubkey, event_count, percentage FROM top_pubkeys_view ORDER BY event_count DESC LIMIT 10", -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            cJSON* pubkey_obj = cJSON_CreateObject();
            const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
            cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
            cJSON_AddNumberToObject(pubkey_obj, "event_count", sqlite3_column_int64(stmt, 1));
            cJSON_AddNumberToObject(pubkey_obj, "percentage", sqlite3_column_double(stmt, 2));
            cJSON_AddItemToArray(top_pubkeys, pubkey_obj);
        }
        sqlite3_finalize(stmt);
    }
    cJSON_AddItemToObject(response, "top_pubkeys", top_pubkeys);

    // Get database creation timestamp (oldest event)
    if (sqlite3_prepare_v2(g_db, "SELECT MIN(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            sqlite3_int64 oldest_timestamp = sqlite3_column_int64(stmt, 0);
            if (oldest_timestamp > 0) {
                cJSON_AddNumberToObject(response, "database_created_at", (double)oldest_timestamp);
            }
        }
        sqlite3_finalize(stmt);
    }

    // Get latest event timestamp
    if (sqlite3_prepare_v2(g_db, "SELECT MAX(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            sqlite3_int64 latest_timestamp = sqlite3_column_int64(stmt, 0);
            if (latest_timestamp > 0) {
                cJSON_AddNumberToObject(response, "latest_event_at", (double)latest_timestamp);
            }
        }
        sqlite3_finalize(stmt);
    }

    // Convert to JSON string
    char* json_string = cJSON_Print(response);
    cJSON_Delete(response);

    if (!json_string) {
        DEBUG_ERROR("Failed to generate stats JSON");
    }

    return json_string;
}

// Unified NIP-17 response sender - handles all common response logic
int send_nip17_response(const char* sender_pubkey, const char* response_content,
                        char* error_message, size_t error_size) {
    if (!sender_pubkey || !response_content || !error_message) {
        if (error_message) {
            strncpy(error_message, "NIP-17: Invalid parameters for response", error_size - 1);
        }
        return -1;
    }

    // Get relay keys for signing
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        if (relay_privkey_hex) free(relay_privkey_hex);
        strncpy(error_message, "NIP-17: Could not get relay keys for response", error_size - 1);
        return -1;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        strncpy(error_message, "NIP-17: Failed to convert relay private key for response", error_size - 1);
        return -1;
    }
    free(relay_privkey_hex);

    // Create DM response event using library function
    cJSON* dm_response = nostr_nip17_create_chat_event(
        response_content,                 // message content
        (const char**)&sender_pubkey,     // recipient pubkeys
        1,                                // num recipients
        NULL,                             // subject (optional)
        NULL,                             // reply_to_event_id (optional)
        NULL,                             // reply_relay_url (optional)
        relay_pubkey                      // sender pubkey
    );

    if (!dm_response) {
        strncpy(error_message, "NIP-17: Failed to create DM response event", error_size - 1);
        return -1;
    }

    // Get timestamp delay configuration
    long max_delay_sec = get_config_int("nip59_timestamp_max_delay_sec", 0);

    // Create and sign gift wrap using library function
    cJSON* gift_wraps[1];
    int send_result = nostr_nip17_send_dm(
        dm_response,                      // dm_event
        (const char**)&sender_pubkey,     // recipient_pubkeys
        1,                                // num_recipients
        relay_privkey,                    // sender_private_key
        gift_wraps,                       // gift_wraps_out
        1,                                // max_gift_wraps
        max_delay_sec                     // max_delay_sec
    );

    cJSON_Delete(dm_response);

    if (send_result != 1 || !gift_wraps[0]) {
        strncpy(error_message, "NIP-17: Failed to create and sign response gift wrap", error_size - 1);
        return -1;
    }

    // Fix the p tag in the gift wrap - library function may use wrong pubkey
    cJSON* gift_wrap_tags = cJSON_GetObjectItem(gift_wraps[0], "tags");
    if (gift_wrap_tags && cJSON_IsArray(gift_wrap_tags)) {
        // Find and replace the p tag with the correct user pubkey
        cJSON* tag = NULL;
        cJSON_ArrayForEach(tag, gift_wrap_tags) {
            if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
                cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
                if (tag_name && cJSON_IsString(tag_name) &&
                    strcmp(cJSON_GetStringValue(tag_name), "p") == 0) {
                    // Replace the p tag value with the correct user pubkey
                    cJSON_ReplaceItemInArray(tag, 1, cJSON_CreateString(sender_pubkey));
                    break;
                }
            }
        }
    }

    // Broadcast FIRST before storing (broadcasting needs the event intact)
    // Make a copy for broadcasting to avoid use-after-free issues
    cJSON* gift_wrap_copy = cJSON_Duplicate(gift_wraps[0], 1);
    if (!gift_wrap_copy) {
        cJSON_Delete(gift_wraps[0]);
        strncpy(error_message, "NIP-17: Failed to duplicate gift wrap for broadcast", error_size - 1);
        return -1;
    }

    // Broadcast the copy to active subscriptions
    broadcast_event_to_subscriptions(gift_wrap_copy);

    // Store the original in database
    int store_result = store_event(gift_wraps[0]);

    // Clean up both copies
    cJSON_Delete(gift_wrap_copy);
    cJSON_Delete(gift_wraps[0]);

    if (store_result != 0) {
        strncpy(error_message, "NIP-17: Failed to store response gift wrap", error_size - 1);
        return -1;
    }

    return 0;
}

// Generate config text from database
char* generate_config_text(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("NIP-17: Database not available for config query");
        return NULL;
    }

    // Build comprehensive config text from database
    char* config_text = malloc(8192);
    if (!config_text) {
        DEBUG_ERROR("NIP-17: Failed to allocate memory for config text");
        return NULL;
    }

    int offset = 0;

    // Header
    offset += snprintf(config_text + offset, 8192 - offset,
        "🔧 Relay Configuration\n"
        "━━━━━━━━━━━━━━━━━━━━━━━━\n");

    // Query all config values from database
    sqlite3_stmt* stmt;
    if (sqlite3_prepare_v2(g_db, "SELECT key, value FROM config ORDER BY key", -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW && offset < 8192 - 200) {
            const char* key = (const char*)sqlite3_column_text(stmt, 0);
            const char* value = (const char*)sqlite3_column_text(stmt, 1);

            if (key && value) {
                offset += snprintf(config_text + offset, 8192 - offset,
                    "%s: %s\n", key, value);
            }
        }
        sqlite3_finalize(stmt);
    } else {
        free(config_text);
        DEBUG_ERROR("NIP-17: Failed to query config from database");
        return NULL;
    }

    // Footer
    offset += snprintf(config_text + offset, 8192 - offset, "\n");

    return config_text;
}

// Helper function to format numbers with commas
void format_number_with_commas(long long num, char* buffer, size_t buffer_size) {
    char temp[32];
    int len = snprintf(temp, sizeof(temp), "%lld", num);
    int comma_count = (len - 1) / 3;
    int result_len = len + comma_count;

    if (result_len >= (int)buffer_size) {
        buffer[0] = '\0';
        return;
    }

    int j = result_len - 1;
    int k = len - 1;
    int comma_pos = 0;

    while (k >= 0) {
        buffer[j--] = temp[k--];
        comma_pos++;
        if (comma_pos % 3 == 0 && k >= 0) {
            buffer[j--] = ',';
        }
    }
    buffer[result_len] = '\0';
}

// Generate human-readable stats text
char* generate_stats_text(void) {
    char* stats_json = generate_stats_json();
    if (!stats_json) {
        DEBUG_ERROR("NIP-17: Failed to generate stats for plain text command");
        return NULL;
    }

    // Parse the JSON to extract values for human-readable format
    cJSON* stats_obj = cJSON_Parse(stats_json);
    char* stats_text = malloc(16384); // Increased buffer size for comprehensive stats
    if (!stats_text) {
        free(stats_json);
        if (stats_obj) cJSON_Delete(stats_obj);
        return NULL;
    }

    if (stats_obj) {
        // Extract basic metrics
        cJSON* total_events = cJSON_GetObjectItem(stats_obj, "total_events");
        cJSON* db_size = cJSON_GetObjectItem(stats_obj, "database_size_bytes");
        cJSON* oldest_event = cJSON_GetObjectItem(stats_obj, "database_created_at");
        cJSON* newest_event = cJSON_GetObjectItem(stats_obj, "latest_event_at");
        cJSON* time_stats = cJSON_GetObjectItem(stats_obj, "time_stats");
        cJSON* event_kinds = cJSON_GetObjectItem(stats_obj, "event_kinds");
        // cJSON* top_pubkeys = cJSON_GetObjectItem(stats_obj, "top_pubkeys");

        long long total = total_events ? (long long)cJSON_GetNumberValue(total_events) : 0;
        long long db_bytes = db_size ? (long long)cJSON_GetNumberValue(db_size) : 0;
        double db_mb = db_bytes / (1024.0 * 1024.0);

        // Get active subscriptions count from in-memory manager
        pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
        int active_subs = g_subscription_manager.total_subscriptions;
        pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);

        // Format timestamps
        char oldest_str[64] = "-";
        char newest_str[64] = "-";
        if (oldest_event && cJSON_GetNumberValue(oldest_event) > 0) {
            time_t oldest_ts = (time_t)cJSON_GetNumberValue(oldest_event);
            struct tm* tm_info = localtime(&oldest_ts);
            strftime(oldest_str, sizeof(oldest_str), "%m/%d/%Y, %I:%M:%S %p", tm_info);
        }
        if (newest_event && cJSON_GetNumberValue(newest_event) > 0) {
            time_t newest_ts = (time_t)cJSON_GetNumberValue(newest_event);
            struct tm* tm_info = localtime(&newest_ts);
            strftime(newest_str, sizeof(newest_str), "%m/%d/%Y, %I:%M:%S %p", tm_info);
        }

        // Extract time-based stats
        long long last_24h = 0, last_7d = 0, last_30d = 0;
        if (time_stats) {
            cJSON* h24 = cJSON_GetObjectItem(time_stats, "last_24h");
            cJSON* d7 = cJSON_GetObjectItem(time_stats, "last_7d");
            cJSON* d30 = cJSON_GetObjectItem(time_stats, "last_30d");
            last_24h = h24 ? (long long)cJSON_GetNumberValue(h24) : 0;
            last_7d = d7 ? (long long)cJSON_GetNumberValue(d7) : 0;
            last_30d = d30 ? (long long)cJSON_GetNumberValue(d30) : 0;
        }

        // Start building the comprehensive stats text
        int offset = 0;

        // Header
        offset += snprintf(stats_text + offset, 16384 - offset,
            "\nRelay Statistics\n"
            "━━━━━━━━━━━━━━━━━━━━\n");

        // Database Overview section
        offset += snprintf(stats_text + offset, 16384 - offset,

 "Database Size  %.2f MB (%lld bytes)\n"
         "Total Events  %lld\n"
         "Active Subscriptions  %d\n"
         "Oldest Event  %s\n"
         "Newest Event  %s\n"
        "\n",
        db_mb, db_bytes, total, active_subs, oldest_str, newest_str);

        // Event Kind Distribution section
        offset += snprintf(stats_text + offset, 16384 - offset,
            "Event_Kind_Distribution:\n");

        if (event_kinds && cJSON_IsArray(event_kinds)) {
            cJSON* kind_item = NULL;
            cJSON_ArrayForEach(kind_item, event_kinds) {
                cJSON* kind = cJSON_GetObjectItem(kind_item, "kind");
                cJSON* count = cJSON_GetObjectItem(kind_item, "count");
                cJSON* percentage = cJSON_GetObjectItem(kind_item, "percentage");

                if (kind && count && percentage) {
                    // Format event kind (right-justified, minimum 5 chars wide with underscores)
                    char kind_str[16];
                    int kind_val = (int)cJSON_GetNumberValue(kind);
                    char temp_kind[16];
                    snprintf(temp_kind, sizeof(temp_kind), "%d", kind_val);

                    // Calculate padding needed (minimum 5 chars, but more if kind is longer)
                    int kind_len = strlen(temp_kind);
                    int min_width = 5;
                    int total_width = (kind_len > min_width) ? kind_len : min_width;
                    int padding = total_width - kind_len;

                    // Create padded string with underscores
                    memset(kind_str, '_', padding);
                    strcpy(kind_str + padding, temp_kind);

                    // Format count with commas (right-justified, 11 chars wide with underscores)
                    char count_str[16];
                    format_number_with_commas((long long)cJSON_GetNumberValue(count), count_str, sizeof(count_str));
                    char count_formatted[16];
                    snprintf(count_formatted, sizeof(count_formatted), "%11s", count_str);
                    // Replace spaces with underscores for alignment
                    for (int i = 0; count_formatted[i]; i++) {
                        if (count_formatted[i] == ' ') count_formatted[i] = '_';
                    }

                    // Format percentage (right-justified, 7 chars wide including %, with underscores)
                    double pct = cJSON_GetNumberValue(percentage);
                    char pct_str[8];
                    snprintf(pct_str, sizeof(pct_str), "%.1f%%", pct);
                    // Pad with underscores to make 7 chars total (right-justified)
                    char pct_formatted[8];
                    int pct_len = strlen(pct_str);
                    int underscores_needed = 7 - pct_len;
                    memset(pct_formatted, '_', underscores_needed);
                    strcpy(pct_formatted + underscores_needed, pct_str);

                    offset += snprintf(stats_text + offset, 16384 - offset,
                        "%s%s_%s\n", kind_str, count_formatted, pct_formatted);
                }
            }
        } else {
            offset += snprintf(stats_text + offset, 16384 - offset,
                "No event data available\n");
        }

        offset += snprintf(stats_text + offset, 16384 - offset, "\n");

        // Time-based Statistics section
        offset += snprintf(stats_text + offset, 16384 - offset,
            "Time-based Statistics:\n"

            "%lld\tEvents in the last day\n"
            "%lld\tEvents in the last week\n"
            "%lld\tEvents in the last month\n"
            "\n",
            last_24h, last_7d, last_30d);

        // Top Pubkeys section
        // offset += snprintf(stats_text + offset, 16384 - offset,
        //     "Top Pubkeys by Event Count:\n"
        //     "Rank\tPubkey\tEvent Count\tPercentage\n");

        // if (top_pubkeys && cJSON_IsArray(top_pubkeys)) {
        //     int rank = 1;
        //     cJSON* pubkey_item = NULL;
        //     cJSON_ArrayForEach(pubkey_item, top_pubkeys) {
        //         cJSON* pubkey = cJSON_GetObjectItem(pubkey_item, "pubkey");
        //         cJSON* event_count = cJSON_GetObjectItem(pubkey_item, "event_count");
        //         cJSON* percentage = cJSON_GetObjectItem(pubkey_item, "percentage");

        //         if (pubkey && event_count && percentage) {
        //             const char* pubkey_str = cJSON_GetStringValue(pubkey);
        //             char short_pubkey[20] = "...";
        //             if (pubkey_str && strlen(pubkey_str) >= 16) {
        //                 snprintf(short_pubkey, sizeof(short_pubkey), "%.16s...", pubkey_str);
        //             }

        //             offset += snprintf(stats_text + offset, 16384 - offset,
        //                 "%d\t%s\t%lld\t%.1f%%\n",
        //                 rank++,
        //                 short_pubkey,
        //                 (long long)cJSON_GetNumberValue(event_count),
        //                 cJSON_GetNumberValue(percentage));
        //         }
        //     }
        // } else {
        //     offset += snprintf(stats_text + offset, 16384 - offset,
        //         "No pubkey data available\n");
        // }

        // Footer
        offset += snprintf(stats_text + offset, 16384 - offset, "\n");

        cJSON_Delete(stats_obj);
    } else {
        // Fallback if JSON parsing fails
        snprintf(stats_text, 16384,
            "📊 Relay Statistics\n"
            "━━━━━━━━━━━━━━━━━━━━\n"
            "Raw data: %s\n"
            "\n"
            "⚠️ Could not parse statistics data",
            stats_json
        );
    }

    free(stats_json);
    return stats_text;
}

// =============================================================================
// CONFIGURATION CHANGE IMPLEMENTATION
// =============================================================================

// Parse configuration command from natural language
// Supports patterns like:
// - "auth_enabled true"
// - "set auth_enabled to true"
// - "change auth_enabled true"
// - "auth_enabled = true"
// - "auth_enabled: true"
// - "enable auth" / "disable auth"
int parse_config_command(const char* message, char* key, char* value) {
    if (!message || !key || !value) {
        return 0;
    }

    // Clean up the message - convert to lowercase and trim
    char clean_msg[512];
    size_t msg_len = strlen(message);
    size_t copy_len = msg_len < sizeof(clean_msg) - 1 ? msg_len : sizeof(clean_msg) - 1;
    memcpy(clean_msg, message, copy_len);
    clean_msg[copy_len] = '\0';

    // Convert to lowercase
    for (size_t i = 0; i < copy_len; i++) {
        if (clean_msg[i] >= 'A' && clean_msg[i] <= 'Z') {
            clean_msg[i] = clean_msg[i] + 32;
        }
    }

    // Remove leading/trailing whitespace
    char* start = clean_msg;
    while (*start == ' ' || *start == '\t') start++;
    char* end = start + strlen(start) - 1;
    while (end > start && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) {
        *end = '\0';
        end--;
    }

    // Pattern 1: "enable auth" -> "auth_enabled true"
    if (strstr(start, "enable auth") == start) {
        strcpy(key, "auth_enabled");
        strcpy(value, "true");
        return 1;
    }

    // Pattern 2: "disable auth" -> "auth_enabled false"
    if (strstr(start, "disable auth") == start) {
        strcpy(key, "auth_enabled");
        strcpy(value, "false");
        return 1;
    }

    // Pattern 3: "enable nip42" -> "nip42_auth_required true"
    if (strstr(start, "enable nip42") == start) {
        strcpy(key, "nip42_auth_required");
        strcpy(value, "true");
        return 1;
    }

    // Pattern 4: "disable nip42" -> "nip42_auth_required false"
    if (strstr(start, "disable nip42") == start) {
        strcpy(key, "nip42_auth_required");
        strcpy(value, "false");
        return 1;
    }

    // Pattern 5: "set KEY to VALUE" or "change KEY to VALUE"
    char* set_pos = strstr(start, "set ");
    char* change_pos = strstr(start, "change ");
    char* to_pos = strstr(start, " to ");

    if ((set_pos == start || change_pos == start) && to_pos) {
        char* key_start = (set_pos == start) ? start + 4 : start + 7;
        size_t key_len = to_pos - key_start;
        if (key_len > 0 && key_len < 127) {
            memcpy(key, key_start, key_len);
            key[key_len] = '\0';

            // Trim key
            char* key_end = key + strlen(key) - 1;
            while (key_end > key && (*key_end == ' ' || *key_end == '\t')) {
                *key_end = '\0';
                key_end--;
            }

            char* value_start = to_pos + 4;
            strcpy(value, value_start);
            return 1;
        }
    }

    // Pattern 6: "KEY = VALUE"
    char* equals_pos = strstr(start, " = ");
    if (equals_pos) {
        size_t key_len = equals_pos - start;
        if (key_len > 0 && key_len < 127) {
            memcpy(key, start, key_len);
            key[key_len] = '\0';
            strcpy(value, equals_pos + 3);
            return 1;
        }
    }

    // Pattern 7: "KEY: VALUE" (colon-separated)
    char* colon_pos = strstr(start, ": ");
    if (colon_pos) {
        size_t key_len = colon_pos - start;
        if (key_len > 0 && key_len < 127) {
            memcpy(key, start, key_len);
            key[key_len] = '\0';
            strcpy(value, colon_pos + 2);
            return 1;
        }
    }

    // Pattern 8: "KEY VALUE" (simple space-separated)
    char* space_pos = strchr(start, ' ');
    if (space_pos) {
        size_t key_len = space_pos - start;
        if (key_len > 0 && key_len < 127) {
            memcpy(key, start, key_len);
            key[key_len] = '\0';
            strcpy(value, space_pos + 1);
            return 1;
        }
    }

    return 0; // No pattern matched
}

// Validate configuration key and value
int validate_config_change(const char* key, const char* value) {
    if (!key || !value) {
        return 0;
    }


    // Find the configuration key
    int found = 0;
    const char* expected_type = NULL;
    int min_val = 0, max_val = 0;

    for (int i = 0; known_configs[i].key != NULL; i++) {
        if (strcmp(key, known_configs[i].key) == 0) {
            found = 1;
            expected_type = known_configs[i].type;
            min_val = known_configs[i].min_val;
            max_val = known_configs[i].max_val;
            break;
        }
    }

    if (!found) {
        return 0; // Unknown configuration key
    }

    // Validate value based on type
    if (strcmp(expected_type, "bool") == 0) {
        if (strcmp(value, "true") == 0 || strcmp(value, "false") == 0 ||
            strcmp(value, "1") == 0 || strcmp(value, "0") == 0 ||
            strcmp(value, "yes") == 0 || strcmp(value, "no") == 0 ||
            strcmp(value, "on") == 0 || strcmp(value, "off") == 0) {
            return 1;
        }
        return 0;
    } else if (strcmp(expected_type, "int") == 0) {
        char* endptr;
        long val = strtol(value, &endptr, 10);
        if (*endptr != '\0') {
            return 0; // Not a valid integer
        }
        if (val < min_val || val > max_val) {
            return 0; // Out of range
        }
        return 1;
    } else if (strcmp(expected_type, "string") == 0) {
        // String values are generally valid, but check length
        if (strlen(value) > 255) {
            return 0; // Too long
        }
        return 1;
    }

    return 0;
}

// Generate a unique change ID based on admin pubkey and timestamp
void generate_change_id(const char* admin_pubkey, char* change_id) {
    char input[128];
    snprintf(input, sizeof(input), "%s_%ld", admin_pubkey, time(NULL));

    // Simple hash - just use first 32 chars of the input
    size_t input_len = strlen(input);
    for (int i = 0; i < 32 && i < (int)input_len; i++) {
        change_id[i] = input[i];
    }
    change_id[32] = '\0';
}

// Store a pending configuration change
char* store_pending_config_change(const char* admin_pubkey, const char* key,
                                 const char* old_value, const char* new_value) {
    if (!admin_pubkey || !key || !old_value || !new_value) {
        return NULL;
    }

    // Clean up expired changes first
    cleanup_expired_pending_changes();

    // Create new pending change
    pending_config_change_t* change = malloc(sizeof(pending_config_change_t));
    if (!change) {
        return NULL;
    }

    strncpy(change->admin_pubkey, admin_pubkey, sizeof(change->admin_pubkey) - 1);
    change->admin_pubkey[sizeof(change->admin_pubkey) - 1] = '\0';

    strncpy(change->config_key, key, sizeof(change->config_key) - 1);
    change->config_key[sizeof(change->config_key) - 1] = '\0';

    strncpy(change->old_value, old_value, sizeof(change->old_value) - 1);
    change->old_value[sizeof(change->old_value) - 1] = '\0';

    strncpy(change->new_value, new_value, sizeof(change->new_value) - 1);
    change->new_value[sizeof(change->new_value) - 1] = '\0';

    change->timestamp = time(NULL);
    generate_change_id(admin_pubkey, change->change_id);

    // Add to linked list
    change->next = pending_changes_head;
    pending_changes_head = change;
    pending_changes_count++;

    // Return a copy of the change ID
    char* change_id_copy = malloc(33);
    if (change_id_copy) {
        strcpy(change_id_copy, change->change_id);
    }
    return change_id_copy;
}

// Find a pending change by admin pubkey and change ID
pending_config_change_t* find_pending_change(const char* admin_pubkey, const char* change_id) {
    if (!admin_pubkey) {
        return NULL;
    }

    pending_config_change_t* current = pending_changes_head;
    while (current) {
        if (strcmp(current->admin_pubkey, admin_pubkey) == 0) {
            if (!change_id || strcmp(current->change_id, change_id) == 0) {
                return current;
            }
        }
        current = current->next;
    }
    return NULL;
}

// Find the most recent pending change for an admin
pending_config_change_t* find_latest_pending_change(const char* admin_pubkey) {
    if (!admin_pubkey) {
        return NULL;
    }

    pending_config_change_t* latest = NULL;
    pending_config_change_t* current = pending_changes_head;

    while (current) {
        if (strcmp(current->admin_pubkey, admin_pubkey) == 0) {
            if (!latest || current->timestamp > latest->timestamp) {
                latest = current;
            }
        }
        current = current->next;
    }
    return latest;
}

// Remove a pending change from the list
void remove_pending_change(pending_config_change_t* change_to_remove) {
    if (!change_to_remove) {
        return;
    }

    if (pending_changes_head == change_to_remove) {
        pending_changes_head = change_to_remove->next;
    } else {
        pending_config_change_t* current = pending_changes_head;
        while (current && current->next != change_to_remove) {
            current = current->next;
        }
        if (current) {
            current->next = change_to_remove->next;
        }
    }

    free(change_to_remove);
    pending_changes_count--;
}

// Clean up expired pending changes (older than 5 minutes)
void cleanup_expired_pending_changes(void) {
    time_t now = time(NULL);
    pending_config_change_t* current = pending_changes_head;

    while (current) {
        pending_config_change_t* next = current->next;
        if (now - current->timestamp > CONFIG_CHANGE_TIMEOUT) {
            remove_pending_change(current);
        }
        current = next;
    }
}

// Apply a configuration change to the database
int apply_config_change(const char* key, const char* value) {
    if (!key || !value) {
        return -1;
    }

    extern sqlite3* g_db;
    if (!g_db) {
        DEBUG_ERROR("Database not available for config change");
        return -1;
    }

    // Normalize boolean values
    char normalized_value[256];
    strncpy(normalized_value, value, sizeof(normalized_value) - 1);
    normalized_value[sizeof(normalized_value) - 1] = '\0';

    // Convert various boolean representations to "true"/"false"
    if (strcmp(value, "1") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "on") == 0) {
        strcpy(normalized_value, "true");
    } else if (strcmp(value, "0") == 0 || strcmp(value, "no") == 0 || strcmp(value, "off") == 0) {
        strcpy(normalized_value, "false");
    }

    // Determine the data type based on the configuration key
    const char* data_type = "string"; // Default to string
    for (int i = 0; known_configs[i].key != NULL; i++) {
        if (strcmp(key, known_configs[i].key) == 0) {
            if (strcmp(known_configs[i].type, "bool") == 0) {
                data_type = "boolean";
            } else if (strcmp(known_configs[i].type, "int") == 0) {
                data_type = "integer";
            } else if (strcmp(known_configs[i].type, "string") == 0) {
                data_type = "string";
            }
            break;
        }
    }

    // Update or insert the configuration value
    sqlite3_stmt* stmt;
    const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)";

    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        DEBUG_ERROR("Failed to prepare config update statement");
        const char* err_msg = sqlite3_errmsg(g_db);
        DEBUG_ERROR(err_msg);
        return -1;
    }

    sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, normalized_value, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_STATIC);

    int result = sqlite3_step(stmt);
    if (result != SQLITE_DONE) {
        DEBUG_ERROR("Failed to update configuration in database");
        const char* err_msg = sqlite3_errmsg(g_db);
        DEBUG_ERROR(err_msg);
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    return 0;
}

// Generate confirmation message for config change
char* generate_config_change_confirmation(const char* key, const char* old_value, const char* new_value) {
    if (!key || !old_value || !new_value) {
        return NULL;
    }

    char* confirmation = malloc(2048);
    if (!confirmation) {
        return NULL;
    }

    // Get description for the config key
    const char* description = "";
    if (strcmp(key, "auth_enabled") == 0) {
        description = "This controls whether authentication is required for the relay.";
    } else if (strcmp(key, "nip42_auth_required") == 0) {
        description = "This controls whether NIP-42 authentication is required.";
    } else if (strcmp(key, "nip40_expiration_enabled") == 0) {
        description = "This controls whether NIP-40 event expiration is enabled.";
    } else if (strcmp(key, "max_connections") == 0) {
        description = "This sets the maximum number of concurrent connections.";
    } else if (strcmp(key, "max_subscriptions_per_client") == 0) {
        description = "This sets the maximum subscriptions per client.";
    } else if (strcmp(key, "pow_min_difficulty") == 0) {
        description = "This sets the minimum proof-of-work difficulty required.";
    } else if (strstr(key, "relay_") == key) {
        description = "This changes relay metadata information.";
    }

    snprintf(confirmation, 2048,
        "🔧 Configuration Change Request\n"
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
        "\n"
        "Setting: %s\n"
        "Current Value: %s\n"
        "New Value: %s\n"
        "\n"
        "%s%s"
        "\n"
        "⚠️ Reply with 'yes' to confirm or 'no' to cancel.\n"
        "⏰ This request will expire in 5 minutes.",
        key, old_value, new_value,
        strlen(description) > 0 ? "ℹ️ " : "",
        description
    );

    return confirmation;
}

// Handle confirmation responses (yes/no)
int handle_config_confirmation(const char* admin_pubkey, const char* response) {
    if (!admin_pubkey || !response) {
        return -1;
    }

    // Clean up expired changes first
    cleanup_expired_pending_changes();

    // Convert response to lowercase
    char response_lower[64];
    size_t response_len = strlen(response);
    size_t copy_len = response_len < sizeof(response_lower) - 1 ? response_len : sizeof(response_lower) - 1;
    memcpy(response_lower, response, copy_len);
    response_lower[copy_len] = '\0';

    for (size_t i = 0; i < copy_len; i++) {
        if (response_lower[i] >= 'A' && response_lower[i] <= 'Z') {
            response_lower[i] = response_lower[i] + 32;
        }
    }

    // Trim whitespace
    char* start = response_lower;
    while (*start == ' ' || *start == '\t') start++;
    char* end = start + strlen(start) - 1;
    while (end > start && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) {
        *end = '\0';
        end--;
    }

    // Check if it's a confirmation response
    int is_yes = (strcmp(start, "yes") == 0 || strcmp(start, "y") == 0 ||
                  strcmp(start, "confirm") == 0 || strcmp(start, "ok") == 0);
    int is_no = (strcmp(start, "no") == 0 || strcmp(start, "n") == 0 ||
                 strcmp(start, "cancel") == 0 || strcmp(start, "abort") == 0);

    if (!is_yes && !is_no) {
        return 0; // Not a confirmation response
    }

    // Find the most recent pending change for this admin
    pending_config_change_t* change = find_latest_pending_change(admin_pubkey);
    if (!change) {
        return -2; // No pending changes
    }

    if (is_yes) {
        // Apply the configuration change
        int result = apply_config_change(change->config_key, change->new_value);
        if (result == 0) {
            // Send success response
            char success_msg[1024];
            snprintf(success_msg, sizeof(success_msg),
                "✅ Configuration Updated\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━\n"
                "\n"
                "%s: %s → %s\n"
                "\n"
                "Change applied successfully.",
                change->config_key, change->old_value, change->new_value
            );

            char error_msg[256];
            int send_result = send_nip17_response(admin_pubkey, success_msg, error_msg, sizeof(error_msg));
            if (send_result != 0) {
                DEBUG_ERROR(error_msg);
            }

            // Remove the pending change
            remove_pending_change(change);
            return 1; // Success
        } else {
            // Send error response
            char error_msg[1024];
            snprintf(error_msg, sizeof(error_msg),
                "❌ Configuration Update Failed\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
                "\n"
                "Failed to apply change: %s = %s\n"
                "\n"
                "Please check the relay logs for details.",
                change->config_key, change->new_value
            );

            char send_error_msg[256];
            int send_result = send_nip17_response(admin_pubkey, error_msg, send_error_msg, sizeof(send_error_msg));
            if (send_result != 0) {
                DEBUG_ERROR(send_error_msg);
            }

            // Remove the pending change
            remove_pending_change(change);
            return -3; // Application failed
        }
    } else if (is_no) {
        // Cancel the change
        char cancel_msg[512];
        snprintf(cancel_msg, sizeof(cancel_msg),
            "🚫 Configuration Change Cancelled\n"
            "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
            "\n"
            "Change cancelled: %s\n"
            "\n"
            "No changes were made to the relay configuration.",
            change->config_key
        );

        send_nip17_response(admin_pubkey, cancel_msg, NULL, 0);

        // Remove the pending change
        remove_pending_change(change);
        return 2; // Cancelled
    }

    return 0;
}

// Process a configuration change request
int process_config_change_request(const char* admin_pubkey, const char* message) {
    if (!admin_pubkey || !message) {
        return -1;
    }

    char key[128], value[256];

    // Parse the configuration command
    if (!parse_config_command(message, key, value)) {
        return 0; // Not a config command
    }

    // Validate the configuration change
    if (!validate_config_change(key, value)) {
        char error_msg[2048];
        snprintf(error_msg, sizeof(error_msg),
            "❌ Invalid Configuration\n"
            "━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
            "\n"
            "Invalid configuration: %s = %s\n"
            "\n"
            "The configuration key '%s' is either unknown or the value '%s' is invalid.\n"
            "\n"
            "Supported keys include: auth_enabled, max_connections, default_limit, relay_description, etc.\n"
            "Use 'config' command to see all current settings.",
            key, value, key, value
        );
        send_nip17_response(admin_pubkey, error_msg, NULL, 0);
        return -2;
    }

    // Get current value
    const char* current_value = get_config_value(key);
    if (!current_value) {
        current_value = "unset";
    }

    // Check if the value is already set to the requested value
    if (strcmp(current_value, value) == 0) {
        char already_set_msg[1024];
        snprintf(already_set_msg, sizeof(already_set_msg),
            "ℹ️ Configuration Already Set\n"
            "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
            "\n"
            "%s is already set to: %s\n"
            "\n"
            "No change needed.",
            key, value
        );
        send_nip17_response(admin_pubkey, already_set_msg, NULL, 0);
        return 3;
    }

    // Store the pending change
    char* change_id = store_pending_config_change(admin_pubkey, key, current_value, value);
    if (!change_id) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg),
            "❌ Failed to store configuration change request.\n"
            "Please try again."
        );
        send_nip17_response(admin_pubkey, error_msg, NULL, 0);
        return -3;
    }

    // Generate and send confirmation message
    char* confirmation = generate_config_change_confirmation(key, current_value, value);
    if (confirmation) {
        char error_msg[256];
        int send_result = send_nip17_response(admin_pubkey, confirmation, error_msg, sizeof(error_msg));
        if (send_result != 0) {
            DEBUG_ERROR(error_msg);
        }
        free(confirmation);
    }

    free(change_id);
    return 1; // Confirmation sent
}

// Forward declarations for relay event creation functions
cJSON* create_relay_metadata_event(cJSON* metadata);
cJSON* create_relay_dm_list_event(cJSON* dm_relays);
cJSON* create_relay_list_event(cJSON* relays);

// Handle create_relay_event admin commands
int handle_create_relay_event_command(cJSON* event, int kind, cJSON* event_data, char* error_message, size_t error_size, struct lws* wsi) {
    if (!event || !event_data || !error_message) {
        return -1;
    }

    // Get request event ID for response correlation
    cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
    if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
        snprintf(error_message, error_size, "Missing request event ID");
        return -1;
    }
    const char* request_id = cJSON_GetStringValue(request_id_obj);

    // Get sender pubkey for response
    cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
        snprintf(error_message, error_size, "Missing sender pubkey");
        return -1;
    }
    const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

    // Create the relay event based on kind
    cJSON* relay_event = NULL;
    switch (kind) {
        case 0: // User metadata
            relay_event = create_relay_metadata_event(event_data);
            break;
        case 10050: // DM relay list
            relay_event = create_relay_dm_list_event(event_data);
            break;
        case 10002: // Relay list
            relay_event = create_relay_list_event(event_data);
            break;
        default: {
            char response_content[256];
            snprintf(response_content, sizeof(response_content),
                    "❌ Unsupported event kind: %d\n\nSupported kinds: 0 (metadata), 10050 (DM relays), 10002 (relays)",
                    kind);
            return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
        }
    }

    if (!relay_event) {
        char response_content[128];
        snprintf(response_content, sizeof(response_content),
                "❌ Failed to create relay event (kind %d)\n\nCheck relay logs for details.", kind);
        return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
    }

    // Store the event in database
    int store_result = store_event(relay_event);
    if (store_result != 0) {
        cJSON_Delete(relay_event);
        char response_content[128];
        snprintf(response_content, sizeof(response_content),
                "❌ Failed to store relay event (kind %d) in database", kind);
        return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
    }

    // Broadcast the event to connected clients
    broadcast_event_to_subscriptions(relay_event);

    // Clean up
    cJSON_Delete(relay_event);

    // Send success response (plain text like other admin commands)
    char response_content[256];
    const char* kind_name = (kind == 0) ? "metadata" : (kind == 10050) ? "DM relay list" : "relay list";
    snprintf(response_content, sizeof(response_content),
            "✅ Relay event created successfully\n\nKind: %d (%s)\n\nEvent has been stored and broadcast to subscribers.",
            kind, kind_name);
    
    return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
}

// Create a relay metadata event (kind 0)
cJSON* create_relay_metadata_event(cJSON* metadata) {
    if (!metadata || !cJSON_IsObject(metadata)) {
        DEBUG_ERROR("Invalid metadata object for kind 0 event");
        return NULL;
    }

    // Get relay keys
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        DEBUG_ERROR("Could not get relay keys for metadata event");
        if (relay_privkey_hex) free(relay_privkey_hex);
        return NULL;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        DEBUG_ERROR("Failed to convert relay private key for metadata event");
        return NULL;
    }
    free(relay_privkey_hex);

    // Create metadata content
    char* content = cJSON_Print(metadata);
    if (!content) {
        DEBUG_ERROR("Failed to serialize metadata for kind 0 event");
        return NULL;
    }

    // Create and sign the event
    cJSON* signed_event = nostr_create_and_sign_event(
        0,                              // kind (metadata)
        content,                        // content
        NULL,                           // tags (none for kind 0)
        relay_privkey,                  // private key
        (time_t)time(NULL)              // timestamp
    );

    free(content);

    if (!signed_event) {
        DEBUG_ERROR("Failed to create and sign metadata event");
        return NULL;
    }

    DEBUG_LOG("Created relay metadata event (kind 0)");
    return signed_event;
}

// Create a relay DM list event (kind 10050)
cJSON* create_relay_dm_list_event(cJSON* dm_relays) {
    if (!dm_relays || !cJSON_IsObject(dm_relays)) {
        DEBUG_ERROR("Invalid DM relays object for kind 10050 event");
        return NULL;
    }

    // Get relay keys
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        DEBUG_ERROR("Could not get relay keys for DM list event");
        if (relay_privkey_hex) free(relay_privkey_hex);
        return NULL;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        DEBUG_ERROR("Failed to convert relay private key for DM list event");
        return NULL;
    }
    free(relay_privkey_hex);

    // Create empty content for kind 10050
    const char* content = "";

    // Create tags from relay list
    cJSON* tags = cJSON_CreateArray();
    if (!tags) {
        DEBUG_ERROR("Failed to create tags array for DM list event");
        return NULL;
    }

    // Extract relays array
    cJSON* relays_array = cJSON_GetObjectItem(dm_relays, "relays");
    if (relays_array && cJSON_IsArray(relays_array)) {
        cJSON* relay_item = NULL;
        cJSON_ArrayForEach(relay_item, relays_array) {
            if (cJSON_IsString(relay_item)) {
                const char* relay_url = cJSON_GetStringValue(relay_item);
                if (relay_url && strlen(relay_url) > 0) {
                    cJSON* tag = cJSON_CreateArray();
                    cJSON_AddItemToArray(tag, cJSON_CreateString("relay"));
                    cJSON_AddItemToArray(tag, cJSON_CreateString(relay_url));
                    cJSON_AddItemToArray(tags, tag);
                }
            }
        }
    }

    // Create and sign the event
    cJSON* signed_event = nostr_create_and_sign_event(
        10050,                          // kind (DM relay list)
        content,                        // content (empty)
        tags,                           // tags
        relay_privkey,                  // private key
        (time_t)time(NULL)              // timestamp
    );

    cJSON_Delete(tags);

    if (!signed_event) {
        DEBUG_ERROR("Failed to create and sign DM list event");
        return NULL;
    }

    DEBUG_LOG("Created relay DM list event (kind 10050)");
    return signed_event;
}

// Create a relay list event (kind 10002)
cJSON* create_relay_list_event(cJSON* relays) {
    if (!relays || !cJSON_IsObject(relays)) {
        DEBUG_ERROR("Invalid relays object for kind 10002 event");
        return NULL;
    }

    // Get relay keys
    const char* relay_pubkey = get_config_value("relay_pubkey");
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_pubkey || !relay_privkey_hex) {
        DEBUG_ERROR("Could not get relay keys for relay list event");
        if (relay_privkey_hex) free(relay_privkey_hex);
        return NULL;
    }

    // Convert relay private key to bytes
    unsigned char relay_privkey[32];
    if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
        free(relay_privkey_hex);
        DEBUG_ERROR("Failed to convert relay private key for relay list event");
        return NULL;
    }
    free(relay_privkey_hex);

    // Create empty content for kind 10002
    const char* content = "";

    // Create tags from relay list
    cJSON* tags = cJSON_CreateArray();
    if (!tags) {
        DEBUG_ERROR("Failed to create tags array for relay list event");
        return NULL;
    }

    // Extract relays array
    cJSON* relays_array = cJSON_GetObjectItem(relays, "relays");
    if (relays_array && cJSON_IsArray(relays_array)) {
        cJSON* relay_item = NULL;
        cJSON_ArrayForEach(relay_item, relays_array) {
            if (cJSON_IsObject(relay_item)) {
                cJSON* url = cJSON_GetObjectItem(relay_item, "url");
                cJSON* read = cJSON_GetObjectItem(relay_item, "read");
                cJSON* write = cJSON_GetObjectItem(relay_item, "write");

                if (url && cJSON_IsString(url)) {
                    const char* relay_url = cJSON_GetStringValue(url);
                    int read_flag = read && cJSON_IsBool(read) ? cJSON_IsTrue(read) : true;
                    int write_flag = write && cJSON_IsBool(write) ? cJSON_IsTrue(write) : true;

                    // Create marker string
                    const char* marker = NULL;
                    if (read_flag && write_flag) {
                        marker = ""; // No marker means both read and write
                    } else if (read_flag) {
                        marker = "read";
                    } else if (write_flag) {
                        marker = "write";
                    } else {
                        // Skip invalid entries
                        continue;
                    }

                    cJSON* tag = cJSON_CreateArray();
                    cJSON_AddItemToArray(tag, cJSON_CreateString("r"));
                    cJSON_AddItemToArray(tag, cJSON_CreateString(relay_url));
                    if (marker[0] != '\0') {
                        cJSON_AddItemToArray(tag, cJSON_CreateString(marker));
                    }
                    cJSON_AddItemToArray(tags, tag);
                }
            }
        }
    }

    // Create and sign the event
    cJSON* signed_event = nostr_create_and_sign_event(
        10002,                          // kind (relay list)
        content,                        // content (empty)
        tags,                           // tags
        relay_privkey,                  // private key
        (time_t)time(NULL)              // timestamp
    );

    cJSON_Delete(tags);

    if (!signed_event) {
        DEBUG_ERROR("Failed to create and sign relay list event");
        return NULL;
    }

    DEBUG_LOG("Created relay list event (kind 10002)");
    return signed_event;
}

// Handle monitoring system admin commands
int handle_monitoring_command(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi) {
    if (!event || !command || !error_message) {
        return -1;
    }

    // Get request event ID for response correlation
    cJSON* request_id_obj = cJSON_GetObjectItem(event, "id");
    if (!request_id_obj || !cJSON_IsString(request_id_obj)) {
        snprintf(error_message, error_size, "Missing request event ID");
        return -1;
    }
    const char* request_id = cJSON_GetStringValue(request_id_obj);

    // Get sender pubkey for response
    cJSON* sender_pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
        snprintf(error_message, error_size, "Missing sender pubkey");
        return -1;
    }
    const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

    // Parse command
    char cmd[256];
    char arg[256];
    cmd[0] = '\0';
    arg[0] = '\0';

    // Simple command parsing - split on space
    const char* space_pos = strchr(command, ' ');
    if (space_pos) {
        size_t cmd_len = space_pos - command;
        if (cmd_len < sizeof(cmd)) {
            memcpy(cmd, command, cmd_len);
            cmd[cmd_len] = '\0';
            strcpy(arg, space_pos + 1);
        }
    } else {
        strcpy(cmd, command);
    }

    // Convert to lowercase for case-insensitive matching
    for (char* p = cmd; *p; p++) {
        if (*p >= 'A' && *p <= 'Z') *p = *p + 32;
    }

    // Handle set_monitoring_throttle command (only remaining monitoring command)
    if (strcmp(cmd, "set_monitoring_throttle") == 0) {
        if (arg[0] == '\0') {
            char* response_content = "❌ Missing throttle value\n\nUsage: set_monitoring_throttle <seconds>";
            return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
        }

        char* endptr;
        long throttle_seconds = strtol(arg, &endptr, 10);
        if (*endptr != '\0' || throttle_seconds < 1 || throttle_seconds > 3600) {
            char* response_content = "❌ Invalid throttle value\n\nThrottle must be between 1 and 3600 seconds.";
            return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
        }

        char throttle_str[16];
        snprintf(throttle_str, sizeof(throttle_str), "%ld", throttle_seconds);

        if (update_config_in_table("kind_24567_reporting_throttle_sec", throttle_str) == 0) {
            char response_content[256];
            snprintf(response_content, sizeof(response_content),
                    "✅ Monitoring throttle updated\n\n"
                    "Minimum interval between monitoring events: %ld seconds\n\n"
                    "ℹ️ Monitoring activates automatically when you subscribe to kind 24567 events.",
                    throttle_seconds);
            return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
        } else {
            char* response_content = "❌ Failed to update monitoring throttle";
            return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
        }
    } else {
        char response_content[1024];
        snprintf(response_content, sizeof(response_content),
                "❌ Unknown monitoring command: %s\n\n"
                "Available command:\n"
                "• set_monitoring_throttle <seconds>\n\n"
                "ℹ️ Monitoring is now subscription-based:\n"
                "Subscribe to kind 24567 events to receive real-time monitoring data.\n"
                "Monitoring automatically activates when subscriptions exist and deactivates when they close.",
                cmd);
        return send_admin_response(sender_pubkey, response_content, request_id, error_message, error_size, wsi);
    }
}