#define _GNU_SOURCE
#include "config.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <cjson/cJSON.h>
#include <libwebsockets.h>

// External database connection (from main.c)
extern sqlite3* g_db;

// Logging functions (defined in main.c)
extern void log_info(const char* message);
extern void log_success(const char* message);
extern void log_warning(const char* message);
extern void log_error(const char* message);

// Forward declarations for unified handlers
extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);

// Forward declarations for tag parsing utilities
extern const char* get_first_tag_name(cJSON* event);
extern const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);

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

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

// Forward declarations for stats generation
extern char* generate_stats_json(void);

// ================================
// CONFIGURATION CHANGE SYSTEM
// ================================

// Data structure for pending configuration changes
typedef struct pending_config_change {
    char admin_pubkey[65];      // Who requested the change
    char config_key[128];       // What config to change
    char old_value[256];        // Current value
    char new_value[256];        // Requested new value
    time_t timestamp;           // When requested
    char change_id[33];         // Unique ID for this change (first 32 chars of hash)
    struct pending_config_change* next; // Linked list for concurrent changes
} pending_config_change_t;

// Global state for pending changes
static pending_config_change_t* pending_changes_head = NULL;
static int pending_changes_count = 0;

// Configuration change timeout (5 minutes)
#define CONFIG_CHANGE_TIMEOUT 300

// Known configuration keys and their types for validation
static struct {
    const char* key;
    const char* type; // "bool", "int", "string"
    int min_val;
    int max_val;
} known_configs[] = {
    {"auth_enabled", "bool", 0, 1},
    {"nip42_auth_required", "bool", 0, 1},
    {"nip40_expiration_enabled", "bool", 0, 1},
    {"max_connections", "int", 1, 10000},
    {"max_subscriptions_per_client", "int", 1, 1000},
    {"max_event_tags", "int", 1, 1000},
    {"max_content_length", "int", 1, 1000000},
    {"max_limit", "int", 1, 10000},
    {"default_limit", "int", 1, 5000},
    {"max_filters_per_subscription", "int", 1, 100},
    {"max_total_subscriptions", "int", 1, 10000},
    {"pow_min_difficulty", "int", 0, 64},
    {"nip42_challenge_timeout", "int", 1, 3600},
    {"nip42_challenge_expiration", "int", 1, 3600},
    {"nip40_expiration_grace_period", "int", 1, 86400},
    {"relay_name", "string", 0, 0},
    {"relay_description", "string", 0, 0},
    {"relay_contact", "string", 0, 0},
    {"relay_icon", "string", 0, 0},
    {"relay_countries", "string", 0, 0},
    {"language_tags", "string", 0, 0},
    {"posting_policy", "string", 0, 0},
    {"payments_url", "string", 0, 0},
    {"supported_nips", "string", 0, 0},
    {"relay_software", "string", 0, 0},
    {"relay_version", "string", 0, 0},
    {"pow_mode", "string", 0, 0},
    {NULL, NULL, 0, 0}
};

// Forward declarations for config change functions
int parse_config_command(const char* message, char* key, char* value);
int validate_config_change(const char* key, const char* value);
char* store_pending_config_change(const char* admin_pubkey, const char* key,
                                 const char* old_value, const char* new_value);
pending_config_change_t* find_pending_change(const char* admin_pubkey, const char* change_id);
int apply_config_change(const char* key, const char* value);
void cleanup_expired_pending_changes(void);
int handle_config_confirmation(const char* admin_pubkey, const char* response);
char* generate_config_change_confirmation(const char* key, const char* old_value, const char* new_value);
int process_config_change_request(const char* admin_pubkey, const char* message);
int send_nip17_response(const char* sender_pubkey, const char* response_content,
                       char* error_message, size_t error_size);

// Forward declarations for admin event processing
extern int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);

// Forward declarations for NIP-17 processing
int is_nip17_gift_wrap_for_relay(cJSON* event);
int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi);
cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi);

// ================================
// DIRECT MESSAGING ADMIN SYSTEM
// ================================

// Process direct command arrays (DM control system)
// This handles commands sent as direct JSON arrays, not wrapped in inner events
int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
    if (!command_array || !cJSON_IsArray(command_array) || !event) {
        log_error("DM Admin: Invalid command array or event");
        snprintf(error_message, error_size, "invalid: null command array or event");
        return -1;
    }

    int array_size = cJSON_GetArraySize(command_array);
    if (array_size < 1) {
        log_error("DM Admin: Empty command array");
        snprintf(error_message, error_size, "invalid: empty command array");
        return -1;
    }

    // Get the command type from the first element
    cJSON* command_item = cJSON_GetArrayItem(command_array, 0);
    if (!command_item || !cJSON_IsString(command_item)) {
        log_error("DM Admin: First element is not a string command");
        snprintf(error_message, error_size, "invalid: command must be a string");
        return -1;
    }

    const char* command_type = cJSON_GetStringValue(command_item);

    // Create synthetic tags from the command array for unified handler compatibility
    cJSON* synthetic_tags = cJSON_CreateArray();

    // Add the command as the first tag
    cJSON* command_tag = cJSON_CreateArray();
    cJSON_AddItemToArray(command_tag, cJSON_CreateString(command_type));

    // Add remaining array elements as tag parameters
    for (int i = 1; i < array_size; i++) {
        cJSON* param = cJSON_GetArrayItem(command_array, i);
        if (param) {
            if (cJSON_IsString(param)) {
                cJSON_AddItemToArray(command_tag, cJSON_CreateString(cJSON_GetStringValue(param)));
            } else {
                // Convert non-string parameters to strings for tag compatibility
                char* param_str = cJSON_Print(param);
                if (param_str) {
                    // Remove quotes from JSON string representation
                    if (param_str[0] == '"' && param_str[strlen(param_str)-1] == '"') {
                        param_str[strlen(param_str)-1] = '\0';
                        cJSON_AddItemToArray(command_tag, cJSON_CreateString(param_str + 1));
                    } else {
                        cJSON_AddItemToArray(command_tag, cJSON_CreateString(param_str));
                    }
                    free(param_str);
                }
            }
        }
    }

    cJSON_AddItemToArray(synthetic_tags, command_tag);

    // Add existing event tags
    cJSON* existing_tags = cJSON_GetObjectItem(event, "tags");
    if (existing_tags && cJSON_IsArray(existing_tags)) {
        cJSON* tag = NULL;
        cJSON_ArrayForEach(tag, existing_tags) {
            cJSON_AddItemToArray(synthetic_tags, cJSON_Duplicate(tag, 1));
        }
    }

    // Temporarily replace event tags with synthetic tags
    cJSON_ReplaceItemInObject(event, "tags", synthetic_tags);

    // Route to appropriate handler based on command type
    int result = -1;

    if (strcmp(command_type, "auth_query") == 0) {
        const char* query_type = get_tag_value(event, "auth_query", 1);
        if (!query_type) {
            log_error("DM Admin: Missing auth_query type parameter");
            snprintf(error_message, error_size, "invalid: missing auth_query type");
        } else {
            result = handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
        }
    }
    else if (strcmp(command_type, "config_query") == 0) {
        const char* query_type = get_tag_value(event, "config_query", 1);
        if (!query_type) {
            log_error("DM Admin: Missing config_query type parameter");
            snprintf(error_message, error_size, "invalid: missing config_query type");
        } else {
            result = handle_config_query_unified(event, query_type, error_message, error_size, wsi);
        }
    }
    else if (strcmp(command_type, "config_set") == 0) {
        const char* config_key = get_tag_value(event, "config_set", 1);
        const char* config_value = get_tag_value(event, "config_set", 2);
        if (!config_key || !config_value) {
            log_error("DM Admin: Missing config_set parameters");
            snprintf(error_message, error_size, "invalid: missing config_set key or value");
        } else {
            result = handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
        }
    }
    else if (strcmp(command_type, "config_update") == 0) {
        result = handle_config_update_unified(event, error_message, error_size, wsi);
    }
    else if (strcmp(command_type, "system_command") == 0) {
        const char* command = get_tag_value(event, "system_command", 1);
        if (!command) {
            log_error("DM Admin: Missing system_command type parameter");
            snprintf(error_message, error_size, "invalid: missing system_command type");
        } else {
            result = handle_system_command_unified(event, command, error_message, error_size, wsi);
        }
    }
    else if (strcmp(command_type, "stats_query") == 0) {
        result = handle_stats_query_unified(event, error_message, error_size, wsi);
    }
    else if (strcmp(command_type, "whitelist") == 0 || strcmp(command_type, "blacklist") == 0) {
        result = handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
    }
    else {
        log_error("DM Admin: Unknown command type");
        printf("  Unknown command: %s\n", command_type);
        snprintf(error_message, error_size, "invalid: unknown DM command type '%s'", command_type);
    }

    if (result == 0) {
        log_success("DM Admin: Command processed successfully");
    } else {
        log_error("DM Admin: Command processing failed");
    }

    return result;
}

// ================================
// 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) {
            log_info("Cleaning up expired config change request");
            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) {
        log_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) {
        log_error("Failed to prepare config update statement");
        const char* err_msg = sqlite3_errmsg(g_db);
        log_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) {
        log_error("Failed to update configuration in database");
        const char* err_msg = sqlite3_errmsg(g_db);
        log_error(err_msg);
        sqlite3_finalize(stmt);
        return -1;
    }

    sqlite3_finalize(stmt);
    char log_msg[512];
    snprintf(log_msg, sizeof(log_msg), "Configuration updated: %s = %s", key, normalized_value);
    log_success(log_msg);
    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) {
                log_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) {
                log_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) {
            log_error(error_msg);
        }
        free(confirmation);
    }

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

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

    log_info("Generating stats JSON from database");

    // 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);

    // 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) {
        log_success("Stats JSON generated successfully");
    } else {
        log_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_relay_pubkey_cached();
    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;
    }

    // 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
    );

    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));
                    log_info("NIP-17: Fixed p tag in response gift wrap");
                    break;
                }
            }
        }
    }

    // Store the gift wrap in database
    int store_result = store_event(gift_wraps[0]);
    
    if (store_result != 0) {
        cJSON_Delete(gift_wraps[0]);
        strncpy(error_message, "NIP-17: Failed to store response gift wrap", error_size - 1);
        return -1;
    }

    // Broadcast the response event to active subscriptions
    int broadcast_count = broadcast_event_to_subscriptions(gift_wraps[0]);
    char debug_broadcast_msg[128];
    snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
             "NIP-17: Response broadcast to %d subscriptions", broadcast_count);
    log_info(debug_broadcast_msg);

    cJSON_Delete(gift_wraps[0]);
    return 0;
}

// Generate config text from database
char* generate_config_text(void) {
    extern sqlite3* g_db;
    if (!g_db) {
        log_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) {
        log_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);
        log_error("NIP-17: Failed to query config from database");
        return NULL;
    }

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

    return config_text;
}

// Generate human-readable stats text
char* generate_stats_text(void) {
    char* stats_json = generate_stats_json();
    if (!stats_json) {
        log_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);

        // 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,
            "📊 Relay Statistics\n"
            "━━━━━━━━━━━━━━━━━━━━\n");

        // Database Overview section
        offset += snprintf(stats_text + offset, 16384 - offset,
            "Database Overview:\n"
            "Metric\tValue\tDescription\n"
            "Database Size\t%.2f MB (%lld bytes)\tCurrent database file size\n"
            "Total Events\t%lld\tTotal number of events stored\n"
            "Oldest Event\t%s\tTimestamp of oldest event\n"
            "Newest Event\t%s\tTimestamp of newest event\n"
            "\n",
            db_mb, db_bytes, total, oldest_str, newest_str);

        // Event Kind Distribution section
        offset += snprintf(stats_text + offset, 16384 - offset,
            "Event Kind Distribution:\n"
            "Event Kind\tCount\tPercentage\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) {
                    offset += snprintf(stats_text + offset, 16384 - offset,
                        "%lld\t%lld\t%.1f%%\n",
                        (long long)cJSON_GetNumberValue(kind),
                        (long long)cJSON_GetNumberValue(count),
                        cJSON_GetNumberValue(percentage));
                }
            }
        } 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"
            "Period\tEvents\tDescription\n"
            "Last 24 Hours\t%lld\tEvents in the last day\n"
            "Last 7 Days\t%lld\tEvents in the last week\n"
            "Last 30 Days\t%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✅ Statistics retrieved successfully");

        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;
}

// Main NIP-17 processing function
cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi) {
    if (!gift_wrap_event || !error_message) {
        return NULL;
    }

    // Step 1: Validate it's addressed to us
    if (!is_nip17_gift_wrap_for_relay(gift_wrap_event)) {
        strncpy(error_message, "NIP-17: Event is not a valid gift wrap for this relay", error_size - 1);
        return NULL;
    }

    // Step 2: Get relay private key for decryption
    char* relay_privkey_hex = get_relay_private_key();
    if (!relay_privkey_hex) {
        strncpy(error_message, "NIP-17: Could not get relay private key for decryption", error_size - 1);
        return NULL;
    }

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

    // Step 3: Decrypt and parse inner event using library function
    log_info("NIP-17: Attempting to decrypt gift wrap with nostr_nip17_receive_dm");
    cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey);
    if (!inner_dm) {
        log_error("NIP-17: nostr_nip17_receive_dm returned NULL");
        // Debug: Print the gift wrap event
        char* gift_wrap_debug = cJSON_Print(gift_wrap_event);
        if (gift_wrap_debug) {
            char debug_msg[1024];
            snprintf(debug_msg, sizeof(debug_msg), "NIP-17: Gift wrap event: %.500s", gift_wrap_debug);
            log_error(debug_msg);
            free(gift_wrap_debug);
        }
        // Debug: Check if private key is valid
        char privkey_hex[65];
        for (int i = 0; i < 32; i++) {
            sprintf(privkey_hex + (i * 2), "%02x", relay_privkey[i]);
        }
        privkey_hex[64] = '\0';
        char privkey_msg[128];
        snprintf(privkey_msg, sizeof(privkey_msg), "NIP-17: Using relay private key: %.16s...", privkey_hex);
        log_info(privkey_msg);

        strncpy(error_message, "NIP-17: Failed to decrypt and parse inner DM event", error_size - 1);
        return NULL;
    }
    log_info("NIP-17: Successfully decrypted gift wrap");

    // Step 4: Process admin command
    int result = process_nip17_admin_command(inner_dm, error_message, error_size, wsi);

    // Step 5: For plain text commands (stats/config), the response is already handled
    // Only create a generic response for other command types that don't handle their own responses
    if (result == 0) {
        // Extract content to check if it's a plain text command
        cJSON* content_obj = cJSON_GetObjectItem(inner_dm, "content");
        if (content_obj && cJSON_IsString(content_obj)) {
            const char* dm_content = cJSON_GetStringValue(content_obj);
            
            // Check if it's a plain text command that already handled its response
            char content_lower[256];
            size_t content_len = strlen(dm_content);
            size_t copy_len = content_len < sizeof(content_lower) - 1 ? content_len : sizeof(content_lower) - 1;
            memcpy(content_lower, dm_content, copy_len);
            content_lower[copy_len] = '\0';
            
            // Convert to lowercase
            for (size_t i = 0; i < copy_len; i++) {
                if (content_lower[i] >= 'A' && content_lower[i] <= 'Z') {
                    content_lower[i] = content_lower[i] + 32;
                }
            }
            
            // If it's a plain text stats or config command, don't create additional response
            if (strstr(content_lower, "stats") != NULL || strstr(content_lower, "statistics") != NULL ||
                strstr(content_lower, "config") != NULL || strstr(content_lower, "configuration") != NULL) {
                log_info("NIP-17: Plain text command already handled response, skipping generic response");
                cJSON_Delete(inner_dm);
                return NULL; // No additional response needed
            }
            
            // Check if it's a JSON array command that might be stats
            cJSON* command_array = cJSON_Parse(dm_content);
            if (command_array && cJSON_IsArray(command_array) && cJSON_GetArraySize(command_array) > 0) {
                cJSON* first_item = cJSON_GetArrayItem(command_array, 0);
                if (cJSON_IsString(first_item) && strcmp(cJSON_GetStringValue(first_item), "stats") == 0) {
                    log_info("NIP-17: JSON stats command already handled response, skipping generic response");
                    cJSON_Delete(command_array);
                    cJSON_Delete(inner_dm);
                    return NULL; // No additional response needed
                }
                cJSON_Delete(command_array);
            }
        }
    } else if (result > 0) {
        // Command was handled and response was sent, don't create generic response
        log_info("NIP-17: Command handled with custom response, skipping generic response");
        cJSON_Delete(inner_dm);
        return NULL;

        // Get sender pubkey for response from the decrypted DM event
        cJSON* sender_pubkey_obj = cJSON_GetObjectItem(inner_dm, "pubkey");
        if (sender_pubkey_obj && cJSON_IsString(sender_pubkey_obj)) {
            const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

            // Create success response using library function
            char response_content[1024];
            snprintf(response_content, sizeof(response_content),
                    "[\"command_processed\", \"success\", \"%s\"]", "NIP-17 admin command executed");

            // Get relay pubkey for creating DM event
            const char* relay_pubkey = get_relay_pubkey_cached();
            if (relay_pubkey) {
                cJSON* success_dm = 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 (success_dm) {
                    cJSON* success_gift_wraps[1];
                    int send_result = nostr_nip17_send_dm(
                        success_dm,                       // dm_event
                        (const char**)&sender_pubkey,     // recipient_pubkeys
                        1,                                // num_recipients
                        relay_privkey,                    // sender_private_key
                        success_gift_wraps,               // gift_wraps_out
                        1                                 // max_gift_wraps
                    );

                    cJSON_Delete(success_dm);

                    if (send_result == 1 && success_gift_wraps[0]) {
                        // Store the response gift wrap in database
                        store_event(success_gift_wraps[0]);

                        // Return the response event for broadcasting
                        cJSON_Delete(inner_dm);
                        return success_gift_wraps[0];
                    }
                }
            }
        }
    }

    cJSON_Delete(inner_dm);
    return NULL;
}

// Check if decrypted content is a direct command array (DM system)
// Returns 1 if it's a valid command array, 0 if it should fall back to inner event parsing
int is_dm_command_array(const char* decrypted_content) {
    if (!decrypted_content) {
        return 0;
    }

    // Quick check: must start with '[' for JSON array
    if (decrypted_content[0] != '[') {
        return 0;
    }

    // Try to parse as JSON array
    cJSON* parsed = cJSON_Parse(decrypted_content);
    if (!parsed) {
        return 0;
    }

    int is_array = cJSON_IsArray(parsed);
    cJSON_Delete(parsed);

    return is_array;
}

// =============================================================================
// NIP-17 GIFT WRAP PROCESSING FUNCTIONS
// =============================================================================

// Check if an event is a NIP-17 gift wrap addressed to this relay
int is_nip17_gift_wrap_for_relay(cJSON* event) {
    if (!event || !cJSON_IsObject(event)) {
        return 0;
    }

    // Check kind
    cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
    if (!kind_obj || !cJSON_IsNumber(kind_obj) || (int)cJSON_GetNumberValue(kind_obj) != 1059) {
        return 0;
    }

    // Check tags for "p" tag with relay pubkey
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    if (!tags || !cJSON_IsArray(tags)) {
        return 0;
    }

    const char* relay_pubkey = get_relay_pubkey_cached();
    if (!relay_pubkey) {
        log_error("NIP-17: Could not get relay pubkey for validation");
        return 0;
    }

    // Look for "p" tag with relay pubkey
    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
            cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
            cJSON* tag_value = cJSON_GetArrayItem(tag, 1);

            if (tag_name && cJSON_IsString(tag_name) &&
                strcmp(cJSON_GetStringValue(tag_name), "p") == 0 &&
                tag_value && cJSON_IsString(tag_value) &&
                strcmp(cJSON_GetStringValue(tag_value), relay_pubkey) == 0) {
                return 1; // Found matching p tag
            }
        }
    }

    return 0; // No matching p tag found
}

// Process NIP-17 admin command from decrypted DM content
int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi) {
    if (!dm_event || !error_message) {
        return -1;
    }

    // Extract content from DM
    cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content");
    if (!content_obj || !cJSON_IsString(content_obj)) {
        strncpy(error_message, "NIP-17: DM missing content", error_size - 1);
        return -1;
    }

    const char* dm_content = cJSON_GetStringValue(content_obj);

    // Check if sender is admin before processing any commands
    cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
    if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
        log_info("NIP-17: DM missing sender pubkey, treating as user DM");
        return 0; // Not an error, just treat as user DM
    }
    const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

    // Check if sender is admin
    const char* admin_pubkey = get_admin_pubkey_cached();
    int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0;

    log_info("NIP-17: Processing admin command from DM content");
    char log_msg[256];
    snprintf(log_msg, sizeof(log_msg), "NIP-17: Received DM content: '%.50s'%s", dm_content, strlen(dm_content) > 50 ? "..." : "");
    log_info(log_msg);

    // Parse DM content as JSON array of commands
    cJSON* command_array = cJSON_Parse(dm_content);
    if (!command_array || !cJSON_IsArray(command_array)) {
        // If content is not a JSON array, check for plain text commands
        if (is_admin) {
            // Convert content to lowercase for case-insensitive matching
            char content_lower[256];
            size_t content_len = strlen(dm_content);
            size_t copy_len = content_len < sizeof(content_lower) - 1 ? content_len : sizeof(content_lower) - 1;
            memcpy(content_lower, dm_content, copy_len);
            content_lower[copy_len] = '\0';

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

            // Check for stats commands
            if (strstr(content_lower, "stats") != NULL || strstr(content_lower, "statistics") != NULL) {
                log_info("NIP-17: Recognized plain text 'stats' command from admin");
                log_info("NIP-17: Action: Generate and send relay statistics");

                char* stats_text = generate_stats_text();
                if (!stats_text) {
                    return -1;
                }

                char error_msg[256];
                int result = send_nip17_response(sender_pubkey, stats_text, error_msg, sizeof(error_msg));
                free(stats_text);
                
                if (result != 0) {
                    log_error(error_msg);
                    return -1;
                }
                
                log_success("NIP-17: Stats command processed successfully, response sent");
                return 0;
            }
            // Check for config commands
            else if (strstr(content_lower, "config") != NULL || strstr(content_lower, "configuration") != NULL) {
                log_info("NIP-17: Recognized plain text 'config' command from admin");
                log_info("NIP-17: Action: Generate and send relay configuration");

                char* config_text = generate_config_text();
                if (!config_text) {
                    return -1;
                }

                char error_msg[256];
                int result = send_nip17_response(sender_pubkey, config_text, error_msg, sizeof(error_msg));
                free(config_text);
                
                if (result != 0) {
                    log_error(error_msg);
                    return -1;
                }
                
                log_success("NIP-17: Config command processed successfully, response sent");
                return 0;
            }
            else {
                // Check if it's a confirmation response (yes/no)
                int confirmation_result = handle_config_confirmation(sender_pubkey, dm_content);
                if (confirmation_result != 0) {
                    if (confirmation_result > 0) {
                        log_success("NIP-17: Configuration confirmation processed successfully");
                    } else if (confirmation_result == -2) {
                        // No pending changes
                        char no_pending_msg[256];
                        snprintf(no_pending_msg, sizeof(no_pending_msg),
                            "❌ No Pending Changes\n"
                            "━━━━━━━━━━━━━━━━━━━━━━\n"
                            "\n"
                            "You don't have any pending configuration changes to confirm.\n"
                            "\n"
                            "Send a configuration command first (e.g., 'auth_enabled true')."
                        );
                        send_nip17_response(sender_pubkey, no_pending_msg, NULL, 0);
                    }
                    return 0;
                }

                // Check if it's a configuration change request
                int config_result = process_config_change_request(sender_pubkey, dm_content);
                if (config_result != 0) {
                    if (config_result > 0) {
                        log_success("NIP-17: Configuration change request processed successfully");
                        return 1; // Return positive value to indicate response was handled
                    } else {
                        log_error("NIP-17: Configuration change request failed");
                        return -1; // Return error to prevent generic success response
                    }
                }

                log_info("NIP-17: Plain text content from admin not recognized as command, treating as user DM");
                return 0; // Admin sent unrecognized plain text, treat as user DM
            }
        } else {
            // Not admin, treat as user DM
            log_info("NIP-17: Content is not JSON array and sender is not admin, treating as user DM");
            return 0;
        }
    }

    // Check if this is a "stats" command
    if (cJSON_GetArraySize(command_array) > 0) {
        cJSON* first_item = cJSON_GetArrayItem(command_array, 0);
        if (cJSON_IsString(first_item) && strcmp(cJSON_GetStringValue(first_item), "stats") == 0) {
            log_info("NIP-17: Processing 'stats' command directly");

            // Get sender pubkey for response
            cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
            if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
                cJSON_Delete(command_array);
                strncpy(error_message, "NIP-17: DM missing sender pubkey", error_size - 1);
                return -1;
            }
            const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);

            // Generate stats JSON (for JSON array commands, use JSON format)
            char* stats_json = generate_stats_json();
            if (!stats_json) {
                cJSON_Delete(command_array);
                strncpy(error_message, "NIP-17: Failed to generate stats", error_size - 1);
                return -1;
            }

            char error_msg[256];
            int result = send_nip17_response(sender_pubkey, stats_json, error_msg, sizeof(error_msg));
            free(stats_json);
            cJSON_Delete(command_array);
            
            if (result != 0) {
                log_error(error_msg);
                strncpy(error_message, error_msg, error_size - 1);
                return -1;
            }
            
            log_success("NIP-17: Stats command processed successfully");
            return 0;
        }
    }

    // For other commands, delegate to existing admin processing
    // Create a synthetic kind 23456 event with the DM content
    cJSON* synthetic_event = cJSON_CreateObject();
    cJSON_AddNumberToObject(synthetic_event, "kind", 23456);
    cJSON_AddStringToObject(synthetic_event, "content", dm_content);

    // Copy pubkey from DM
    cJSON* pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
    if (pubkey_obj && cJSON_IsString(pubkey_obj)) {
        cJSON_AddStringToObject(synthetic_event, "pubkey", cJSON_GetStringValue(pubkey_obj));
    }

    // Copy tags from DM
    cJSON* tags = cJSON_GetObjectItem(dm_event, "tags");
    if (tags) {
        cJSON_AddItemToObject(synthetic_event, "tags", cJSON_Duplicate(tags, 1));
    }

    // Process as regular admin event
    int result = process_admin_event_in_config(synthetic_event, error_message, error_size, wsi);

    cJSON_Delete(synthetic_event);
    cJSON_Delete(command_array);

    return result;
}