

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <sqlite3.h>
#include <libwebsockets.h>

// Include nostr_core_lib for Nostr functionality
#include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip013.h"  // NIP-13: Proof of Work
#include "config.h"  // Configuration management system

// Color constants for logging
#define RED     "\033[31m"
#define GREEN   "\033[32m"
#define YELLOW  "\033[33m"
#define BLUE    "\033[34m"
#define BOLD    "\033[1m"
#define RESET   "\033[0m"

// Global state
sqlite3* g_db = NULL;  // Non-static so config.c can access it
static int g_server_running = 1;
static struct lws_context *ws_context = NULL;

// NIP-11 relay information structure
struct relay_info {
    char name[RELAY_NAME_MAX_LENGTH];
    char description[RELAY_DESCRIPTION_MAX_LENGTH];
    char banner[RELAY_URL_MAX_LENGTH];
    char icon[RELAY_URL_MAX_LENGTH];
    char pubkey[RELAY_PUBKEY_MAX_LENGTH];
    char contact[RELAY_CONTACT_MAX_LENGTH];
    char software[RELAY_URL_MAX_LENGTH];
    char version[64];
    char privacy_policy[RELAY_URL_MAX_LENGTH];
    char terms_of_service[RELAY_URL_MAX_LENGTH];
    cJSON* supported_nips;      // Array of supported NIP numbers
    cJSON* limitation;          // Server limitations object
    cJSON* retention;           // Event retention policies array
    cJSON* relay_countries;     // Array of country codes
    cJSON* language_tags;       // Array of language tags
    cJSON* tags;               // Array of content tags
    char posting_policy[RELAY_URL_MAX_LENGTH];
    cJSON* fees;               // Payment fee structure
    char payments_url[RELAY_URL_MAX_LENGTH];
};

// Global relay information instance
static struct relay_info g_relay_info = {0};

// NIP-13 PoW configuration structure
struct pow_config {
    int enabled;                    // 0 = disabled, 1 = enabled
    int min_pow_difficulty;         // Minimum required difficulty (0 = no requirement)
    int validation_flags;           // Bitflags for validation options
    int require_nonce_tag;          // 1 = require nonce tag presence
    int reject_lower_targets;       // 1 = reject if committed < actual difficulty
    int strict_format;              // 1 = enforce strict nonce tag format
    int anti_spam_mode;             // 1 = full anti-spam validation
};

// Global PoW configuration instance
static struct pow_config g_pow_config = {
    .enabled = 1,                   // Enable PoW validation by default
    .min_pow_difficulty = 0,        // No minimum difficulty by default
    .validation_flags = NOSTR_POW_VALIDATE_BASIC,
    .require_nonce_tag = 0,         // Don't require nonce tags by default
    .reject_lower_targets = 0,      // Allow lower committed targets by default
    .strict_format = 0,             // Relaxed format validation by default
    .anti_spam_mode = 0            // Basic validation by default
};

// NIP-40 Expiration configuration structure
struct expiration_config {
    int enabled;                    // 0 = disabled, 1 = enabled
    int strict_mode;                // 1 = reject expired events on submission
    int filter_responses;           // 1 = filter expired events from responses
    int delete_expired;             // 1 = delete expired events from DB (future feature)
    long grace_period;              // Grace period in seconds for clock skew
};

// Global expiration configuration instance
static struct expiration_config g_expiration_config = {
    .enabled = 1,                   // Enable expiration handling by default
    .strict_mode = 1,               // Reject expired events on submission by default
    .filter_responses = 1,          // Filter expired events from responses by default
    .delete_expired = 0,            // Don't delete by default (keep for audit)
    .grace_period = 300             // 5 minutes grace period for clock skew
};


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             DATA STRUCTURES
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Forward declarations
typedef struct subscription_filter subscription_filter_t;
typedef struct subscription subscription_t;
typedef struct subscription_manager subscription_manager_t;

// Subscription filter structure
struct subscription_filter {
    // Filter criteria (all optional)
    cJSON* kinds;           // Array of event kinds [1,2,3]
    cJSON* authors;         // Array of author pubkeys
    cJSON* ids;            // Array of event IDs
    long since;            // Unix timestamp (0 = not set)
    long until;            // Unix timestamp (0 = not set)
    int limit;             // Result limit (0 = no limit)
    cJSON* tag_filters;    // Object with tag filters: {"#e": ["id1"], "#p": ["pubkey1"]}
    
    // Linked list for multiple filters per subscription
    struct subscription_filter* next;
};

// Active subscription structure
struct subscription {
    char id[SUBSCRIPTION_ID_MAX_LENGTH];     // Subscription ID
    struct lws* wsi;                         // WebSocket connection handle
    subscription_filter_t* filters;          // Linked list of filters (OR'd together)
    time_t created_at;                       // When subscription was created
    int events_sent;                         // Counter for sent events
    int active;                              // 1 = active, 0 = closed
    
    // Client info for logging
    char client_ip[CLIENT_IP_MAX_LENGTH];    // Client IP address
    
    // Linked list pointers
    struct subscription* next;               // Next subscription globally
    struct subscription* session_next;      // Next subscription for this session
};

// Enhanced per-session data with subscription management
struct per_session_data {
    int authenticated;
    subscription_t* subscriptions;           // Head of this session's subscription list
    pthread_mutex_t session_lock;           // Per-session thread safety
    char client_ip[CLIENT_IP_MAX_LENGTH];   // Client IP for logging
    int subscription_count;                  // Number of subscriptions for this session
};

// Global subscription manager
struct subscription_manager {
    subscription_t* active_subscriptions;   // Head of global subscription list
    pthread_mutex_t subscriptions_lock;     // Global thread safety
    int total_subscriptions;                // Current count
    
    // Configuration
    int max_subscriptions_per_client;       // Default: 20
    int max_total_subscriptions;            // Default: 5000
    
    // Statistics
    uint64_t total_created;                 // Lifetime subscription count
    uint64_t total_events_broadcast;        // Lifetime event broadcast count
};

// Global subscription manager instance
static subscription_manager_t g_subscription_manager = {
    .active_subscriptions = NULL,
    .subscriptions_lock = PTHREAD_MUTEX_INITIALIZER,
    .total_subscriptions = 0,
    .max_subscriptions_per_client = MAX_SUBSCRIPTIONS_PER_CLIENT, // Will be updated from config
    .max_total_subscriptions = MAX_TOTAL_SUBSCRIPTIONS, // Will be updated from config
    .total_created = 0,
    .total_events_broadcast = 0
};

// Forward declarations for logging functions
void log_info(const char* message);
void log_success(const char* message);
void log_error(const char* message);
void log_warning(const char* message);

// Forward declaration for subscription manager configuration
void update_subscription_manager_config(void);

// Forward declarations for subscription database logging
void log_subscription_created(const subscription_t* sub);
void log_subscription_closed(const char* sub_id, const char* client_ip, const char* reason);
void log_subscription_disconnected(const char* client_ip);
void log_event_broadcast(const char* event_id, const char* sub_id, const char* client_ip);
void update_subscription_events_sent(const char* sub_id, int events_sent);

// Forward declarations for NIP-01 event handling
const char* extract_d_tag_value(cJSON* tags);
int check_and_handle_replaceable_event(int kind, const char* pubkey, long created_at);
int check_and_handle_addressable_event(int kind, const char* pubkey, const char* d_tag_value, long created_at);
int handle_event_message(cJSON* event, char* error_message, size_t error_size);

// Forward declaration for NOTICE message support
void send_notice_message(struct lws* wsi, const char* message);

// Forward declarations for NIP-09 deletion request handling
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size);
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids);
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp);
int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, const char* reason);

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

// Forward declarations for NIP-11 relay information handling
void init_relay_info();
void cleanup_relay_info();
cJSON* generate_relay_info_json();
int handle_nip11_http_request(struct lws* wsi, const char* accept_header);

// Forward declarations for NIP-13 PoW validation
void init_pow_config();
int validate_event_pow(cJSON* event, char* error_message, size_t error_size);

// Forward declarations for NIP-40 expiration handling
void init_expiration_config();
long extract_expiration_timestamp(cJSON* tags);
int is_event_expired(cJSON* event, time_t current_time);
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size);


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             PERSISTENT SUBSCRIPTIONS SYSTEM
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Create a subscription filter from cJSON filter object
subscription_filter_t* create_subscription_filter(cJSON* filter_json) {
    if (!filter_json || !cJSON_IsObject(filter_json)) {
        return NULL;
    }
    
    subscription_filter_t* filter = calloc(1, sizeof(subscription_filter_t));
    if (!filter) {
        return NULL;
    }
    
    // Copy filter criteria
    cJSON* kinds = cJSON_GetObjectItem(filter_json, "kinds");
    if (kinds && cJSON_IsArray(kinds)) {
        filter->kinds = cJSON_Duplicate(kinds, 1);
    }
    
    cJSON* authors = cJSON_GetObjectItem(filter_json, "authors");
    if (authors && cJSON_IsArray(authors)) {
        filter->authors = cJSON_Duplicate(authors, 1);
    }
    
    cJSON* ids = cJSON_GetObjectItem(filter_json, "ids");
    if (ids && cJSON_IsArray(ids)) {
        filter->ids = cJSON_Duplicate(ids, 1);
    }
    
    cJSON* since = cJSON_GetObjectItem(filter_json, "since");
    if (since && cJSON_IsNumber(since)) {
        filter->since = (long)cJSON_GetNumberValue(since);
    }
    
    cJSON* until = cJSON_GetObjectItem(filter_json, "until");
    if (until && cJSON_IsNumber(until)) {
        filter->until = (long)cJSON_GetNumberValue(until);
    }
    
    cJSON* limit = cJSON_GetObjectItem(filter_json, "limit");
    if (limit && cJSON_IsNumber(limit)) {
        filter->limit = (int)cJSON_GetNumberValue(limit);
    }
    
    // Handle tag filters (e.g., {"#e": ["id1"], "#p": ["pubkey1"]})
    cJSON* item = NULL;
    cJSON_ArrayForEach(item, filter_json) {
        if (item->string && strlen(item->string) >= 2 && item->string[0] == '#') {
            if (!filter->tag_filters) {
                filter->tag_filters = cJSON_CreateObject();
            }
            if (filter->tag_filters) {
                cJSON_AddItemToObject(filter->tag_filters, item->string, cJSON_Duplicate(item, 1));
            }
        }
    }
    
    return filter;
}

// Free a subscription filter
void free_subscription_filter(subscription_filter_t* filter) {
    if (!filter) return;
    
    if (filter->kinds) cJSON_Delete(filter->kinds);
    if (filter->authors) cJSON_Delete(filter->authors);
    if (filter->ids) cJSON_Delete(filter->ids);
    if (filter->tag_filters) cJSON_Delete(filter->tag_filters);
    
    if (filter->next) {
        free_subscription_filter(filter->next);
    }
    
    free(filter);
}

// Create a new subscription
subscription_t* create_subscription(const char* sub_id, struct lws* wsi, cJSON* filters_array, const char* client_ip) {
    if (!sub_id || !wsi || !filters_array) {
        return NULL;
    }
    
    subscription_t* sub = calloc(1, sizeof(subscription_t));
    if (!sub) {
        return NULL;
    }
    
    // Copy subscription ID (truncate if too long)
    strncpy(sub->id, sub_id, SUBSCRIPTION_ID_MAX_LENGTH - 1);
    sub->id[SUBSCRIPTION_ID_MAX_LENGTH - 1] = '\0';
    
    // Set WebSocket connection
    sub->wsi = wsi;
    
    // Set client IP
    if (client_ip) {
        strncpy(sub->client_ip, client_ip, CLIENT_IP_MAX_LENGTH - 1);
        sub->client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
    }
    
    // Set timestamps and state
    sub->created_at = time(NULL);
    sub->events_sent = 0;
    sub->active = 1;
    
    // Convert filters array to linked list
    subscription_filter_t* filter_tail = NULL;
    int filter_count = 0;
    
    if (cJSON_IsArray(filters_array)) {
        cJSON* filter_json = NULL;
        cJSON_ArrayForEach(filter_json, filters_array) {
            if (filter_count >= MAX_FILTERS_PER_SUBSCRIPTION) {
                log_warning("Maximum filters per subscription exceeded, ignoring excess filters");
                break;
            }
            
            subscription_filter_t* filter = create_subscription_filter(filter_json);
            if (filter) {
                if (!sub->filters) {
                    sub->filters = filter;
                    filter_tail = filter;
                } else {
                    filter_tail->next = filter;
                    filter_tail = filter;
                }
                filter_count++;
            }
        }
    }
    
    if (filter_count == 0) {
        log_error("No valid filters found for subscription");
        free(sub);
        return NULL;
    }
    
    return sub;
}

// Free a subscription
void free_subscription(subscription_t* sub) {
    if (!sub) return;
    
    if (sub->filters) {
        free_subscription_filter(sub->filters);
    }
    
    free(sub);
}

// Add subscription to global manager (thread-safe)
int add_subscription_to_manager(subscription_t* sub) {
    if (!sub) return -1;
    
    pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
    
    // Check global limits
    if (g_subscription_manager.total_subscriptions >= g_subscription_manager.max_total_subscriptions) {
        pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
        log_error("Maximum total subscriptions reached");
        return -1;
    }
    
    // Add to global list
    sub->next = g_subscription_manager.active_subscriptions;
    g_subscription_manager.active_subscriptions = sub;
    g_subscription_manager.total_subscriptions++;
    g_subscription_manager.total_created++;
    
    pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
    
    // Log subscription creation to database
    log_subscription_created(sub);
    
    char debug_msg[256];
    snprintf(debug_msg, sizeof(debug_msg), "Added subscription '%s' (total: %d)",
             sub->id, g_subscription_manager.total_subscriptions);
    log_info(debug_msg);
    
    return 0;
}

// Remove subscription from global manager (thread-safe)
int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
    if (!sub_id) return -1;
    
    pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
    
    subscription_t** current = &g_subscription_manager.active_subscriptions;
    
    while (*current) {
        subscription_t* sub = *current;
        
        // Match by ID and WebSocket connection
        if (strcmp(sub->id, sub_id) == 0 && (!wsi || sub->wsi == wsi)) {
            // Remove from list
            *current = sub->next;
            g_subscription_manager.total_subscriptions--;
            
            pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
            
            // Log subscription closure to database
            log_subscription_closed(sub_id, sub->client_ip, "closed");
            
            // Update events sent counter before freeing
            update_subscription_events_sent(sub_id, sub->events_sent);
            
            char debug_msg[256];
            snprintf(debug_msg, sizeof(debug_msg), "Removed subscription '%s' (total: %d)",
                     sub_id, g_subscription_manager.total_subscriptions);
            log_info(debug_msg);
            
            free_subscription(sub);
            return 0;
        }
        
        current = &(sub->next);
    }
    
    pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
    
    char debug_msg[256];
    snprintf(debug_msg, sizeof(debug_msg), "Subscription '%s' not found for removal", sub_id);
    log_warning(debug_msg);
    
    return -1;
}

// Check if an event matches a subscription filter
int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
    if (!event || !filter) {
        return 0;
    }
    
    // Check kinds filter
    if (filter->kinds && cJSON_IsArray(filter->kinds)) {
        cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
        if (!event_kind || !cJSON_IsNumber(event_kind)) {
            return 0;
        }
        
        int event_kind_val = (int)cJSON_GetNumberValue(event_kind);
        int kind_match = 0;
        
        cJSON* kind_item = NULL;
        cJSON_ArrayForEach(kind_item, filter->kinds) {
            if (cJSON_IsNumber(kind_item) && (int)cJSON_GetNumberValue(kind_item) == event_kind_val) {
                kind_match = 1;
                break;
            }
        }
        
        if (!kind_match) {
            return 0;
        }
    }
    
    // Check authors filter
    if (filter->authors && cJSON_IsArray(filter->authors)) {
        cJSON* event_pubkey = cJSON_GetObjectItem(event, "pubkey");
        if (!event_pubkey || !cJSON_IsString(event_pubkey)) {
            return 0;
        }
        
        const char* event_pubkey_str = cJSON_GetStringValue(event_pubkey);
        int author_match = 0;
        
        cJSON* author_item = NULL;
        cJSON_ArrayForEach(author_item, filter->authors) {
            if (cJSON_IsString(author_item)) {
                const char* author_str = cJSON_GetStringValue(author_item);
                // Support prefix matching (partial pubkeys)
                if (strncmp(event_pubkey_str, author_str, strlen(author_str)) == 0) {
                    author_match = 1;
                    break;
                }
            }
        }
        
        if (!author_match) {
            return 0;
        }
    }
    
    // Check IDs filter
    if (filter->ids && cJSON_IsArray(filter->ids)) {
        cJSON* event_id = cJSON_GetObjectItem(event, "id");
        if (!event_id || !cJSON_IsString(event_id)) {
            return 0;
        }
        
        const char* event_id_str = cJSON_GetStringValue(event_id);
        int id_match = 0;
        
        cJSON* id_item = NULL;
        cJSON_ArrayForEach(id_item, filter->ids) {
            if (cJSON_IsString(id_item)) {
                const char* id_str = cJSON_GetStringValue(id_item);
                // Support prefix matching (partial IDs)
                if (strncmp(event_id_str, id_str, strlen(id_str)) == 0) {
                    id_match = 1;
                    break;
                }
            }
        }
        
        if (!id_match) {
            return 0;
        }
    }
    
    // Check since filter
    if (filter->since > 0) {
        cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at");
        if (!event_created_at || !cJSON_IsNumber(event_created_at)) {
            return 0;
        }
        
        long event_timestamp = (long)cJSON_GetNumberValue(event_created_at);
        if (event_timestamp < filter->since) {
            return 0;
        }
    }
    
    // Check until filter
    if (filter->until > 0) {
        cJSON* event_created_at = cJSON_GetObjectItem(event, "created_at");
        if (!event_created_at || !cJSON_IsNumber(event_created_at)) {
            return 0;
        }
        
        long event_timestamp = (long)cJSON_GetNumberValue(event_created_at);
        if (event_timestamp > filter->until) {
            return 0;
        }
    }
    
    // Check tag filters (e.g., #e, #p tags)
    if (filter->tag_filters && cJSON_IsObject(filter->tag_filters)) {
        cJSON* event_tags = cJSON_GetObjectItem(event, "tags");
        if (!event_tags || !cJSON_IsArray(event_tags)) {
            return 0; // Event has no tags but filter requires tags
        }
        
        // Check each tag filter
        cJSON* tag_filter = NULL;
        cJSON_ArrayForEach(tag_filter, filter->tag_filters) {
            if (!tag_filter->string || strlen(tag_filter->string) < 2 || tag_filter->string[0] != '#') {
                continue; // Invalid tag filter
            }
            
            const char* tag_name = tag_filter->string + 1; // Skip the '#'
            
            if (!cJSON_IsArray(tag_filter)) {
                continue; // Tag filter must be an array
            }
            
            int tag_match = 0;
            
            // Search through event tags for matching tag name and value
            cJSON* event_tag = NULL;
            cJSON_ArrayForEach(event_tag, event_tags) {
                if (!cJSON_IsArray(event_tag) || cJSON_GetArraySize(event_tag) < 2) {
                    continue; // Invalid tag format
                }
                
                cJSON* event_tag_name = cJSON_GetArrayItem(event_tag, 0);
                cJSON* event_tag_value = cJSON_GetArrayItem(event_tag, 1);
                
                if (!cJSON_IsString(event_tag_name) || !cJSON_IsString(event_tag_value)) {
                    continue;
                }
                
                // Check if tag name matches
                if (strcmp(cJSON_GetStringValue(event_tag_name), tag_name) == 0) {
                    const char* event_tag_value_str = cJSON_GetStringValue(event_tag_value);
                    
                    // Check if any of the filter values match this tag value
                    cJSON* filter_value = NULL;
                    cJSON_ArrayForEach(filter_value, tag_filter) {
                        if (cJSON_IsString(filter_value)) {
                            const char* filter_value_str = cJSON_GetStringValue(filter_value);
                            // Support prefix matching for tag values
                            if (strncmp(event_tag_value_str, filter_value_str, strlen(filter_value_str)) == 0) {
                                tag_match = 1;
                                break;
                            }
                        }
                    }
                    
                    if (tag_match) {
                        break;
                    }
                }
            }
            
            if (!tag_match) {
                return 0; // This tag filter didn't match, so the event doesn't match
            }
        }
    }
    
    return 1; // All filters passed
}

// Check if an event matches any filter in a subscription (filters are OR'd together)
int event_matches_subscription(cJSON* event, subscription_t* subscription) {
    if (!event || !subscription || !subscription->filters) {
        return 0;
    }
    
    subscription_filter_t* filter = subscription->filters;
    while (filter) {
        if (event_matches_filter(event, filter)) {
            return 1; // Match found (OR logic)
        }
        filter = filter->next;
    }
    
    return 0; // No filters matched
}

// Broadcast event to all matching subscriptions (thread-safe)
int broadcast_event_to_subscriptions(cJSON* event) {
    if (!event) {
        return 0;
    }
    
    // Check if event is expired and should not be broadcast (NIP-40)
    if (g_expiration_config.enabled && g_expiration_config.filter_responses) {
        time_t current_time = time(NULL);
        if (is_event_expired(event, current_time)) {
            char debug_msg[256];
            cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
            const char* event_id = event_id_obj ? cJSON_GetStringValue(event_id_obj) : "unknown";
            snprintf(debug_msg, sizeof(debug_msg), "Skipping broadcast of expired event: %.16s", event_id);
            log_info(debug_msg);
            return 0; // Don't broadcast expired events
        }
    }
    
    int broadcasts = 0;
    
    pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
    
    subscription_t* sub = g_subscription_manager.active_subscriptions;
    while (sub) {
        if (sub->active && event_matches_subscription(event, sub)) {
            // Create EVENT message for this subscription
            cJSON* event_msg = cJSON_CreateArray();
            cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT"));
            cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub->id));
            cJSON_AddItemToArray(event_msg, cJSON_Duplicate(event, 1));
            
            char* msg_str = cJSON_Print(event_msg);
            if (msg_str) {
                size_t msg_len = strlen(msg_str);
                unsigned char* buf = malloc(LWS_PRE + msg_len);
                if (buf) {
                    memcpy(buf + LWS_PRE, msg_str, msg_len);
                    
                    // Send to WebSocket connection
                    int write_result = lws_write(sub->wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
                    if (write_result >= 0) {
                        sub->events_sent++;
                        broadcasts++;
                        
                        // Log event broadcast to database (optional - can be disabled for performance)
                        cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
                        if (event_id_obj && cJSON_IsString(event_id_obj)) {
                            log_event_broadcast(cJSON_GetStringValue(event_id_obj), sub->id, sub->client_ip);
                        }
                    }
                    
                    free(buf);
                }
                free(msg_str);
            }
            
            cJSON_Delete(event_msg);
        }
        
        sub = sub->next;
    }
    
    // Update global statistics
    g_subscription_manager.total_events_broadcast += broadcasts;
    
    pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
    
    if (broadcasts > 0) {
        char debug_msg[256];
        snprintf(debug_msg, sizeof(debug_msg), "Broadcasted event to %d subscriptions", broadcasts);
        log_info(debug_msg);
    }
    
    return broadcasts;
}


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             SUBSCRIPTION DATABASE LOGGING
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Log subscription creation to database
void log_subscription_created(const subscription_t* sub) {
    if (!g_db || !sub) return;
    
    // Create filter JSON for logging
    char* filter_json = NULL;
    if (sub->filters) {
        cJSON* filters_array = cJSON_CreateArray();
        subscription_filter_t* filter = sub->filters;
        
        while (filter) {
            cJSON* filter_obj = cJSON_CreateObject();
            
            if (filter->kinds) {
                cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
            }
            if (filter->authors) {
                cJSON_AddItemToObject(filter_obj, "authors", cJSON_Duplicate(filter->authors, 1));
            }
            if (filter->ids) {
                cJSON_AddItemToObject(filter_obj, "ids", cJSON_Duplicate(filter->ids, 1));
            }
            if (filter->since > 0) {
                cJSON_AddNumberToObject(filter_obj, "since", filter->since);
            }
            if (filter->until > 0) {
                cJSON_AddNumberToObject(filter_obj, "until", filter->until);
            }
            if (filter->limit > 0) {
                cJSON_AddNumberToObject(filter_obj, "limit", filter->limit);
            }
            if (filter->tag_filters) {
                cJSON* tags_obj = cJSON_Duplicate(filter->tag_filters, 1);
                cJSON* item = NULL;
                cJSON_ArrayForEach(item, tags_obj) {
                    if (item->string) {
                        cJSON_AddItemToObject(filter_obj, item->string, cJSON_Duplicate(item, 1));
                    }
                }
                cJSON_Delete(tags_obj);
            }
            
            cJSON_AddItemToArray(filters_array, filter_obj);
            filter = filter->next;
        }
        
        filter_json = cJSON_Print(filters_array);
        cJSON_Delete(filters_array);
    }
    
    const char* sql =
        "INSERT INTO subscription_events (subscription_id, client_ip, event_type, filter_json) "
        "VALUES (?, ?, 'created', ?)";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, sub->id, -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 2, sub->client_ip, -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 3, filter_json ? filter_json : "[]", -1, SQLITE_TRANSIENT);
        
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    }
    
    if (filter_json) free(filter_json);
}

// Log subscription closure to database
void log_subscription_closed(const char* sub_id, const char* client_ip, const char* reason) {
    (void)reason; // Mark as intentionally unused
    if (!g_db || !sub_id) return;
    
    const char* sql =
        "INSERT INTO subscription_events (subscription_id, client_ip, event_type) "
        "VALUES (?, ?, 'closed')";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 2, client_ip ? client_ip : "unknown", -1, SQLITE_STATIC);
        
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    }
    
    // Update the corresponding 'created' entry with end time and events sent
    const char* update_sql =
        "UPDATE subscription_events "
        "SET ended_at = strftime('%s', 'now') "
        "WHERE subscription_id = ? AND event_type = 'created' AND ended_at IS NULL";
    
    rc = sqlite3_prepare_v2(g_db, update_sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    }
}

// Log subscription disconnection to database
void log_subscription_disconnected(const char* client_ip) {
    if (!g_db || !client_ip) return;
    
    // Mark all active subscriptions for this client as disconnected
    const char* sql =
        "UPDATE subscription_events "
        "SET ended_at = strftime('%s', 'now') "
        "WHERE client_ip = ? AND event_type = 'created' AND ended_at IS NULL";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
        int changes = sqlite3_changes(g_db);
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
        
        if (changes > 0) {
            // Log a disconnection event
            const char* insert_sql =
                "INSERT INTO subscription_events (subscription_id, client_ip, event_type) "
                "VALUES ('disconnect', ?, 'disconnected')";
            
            rc = sqlite3_prepare_v2(g_db, insert_sql, -1, &stmt, NULL);
            if (rc == SQLITE_OK) {
                sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
                sqlite3_step(stmt);
                sqlite3_finalize(stmt);
            }
        }
    }
}

// Log event broadcast to database (optional, can be resource intensive)
void log_event_broadcast(const char* event_id, const char* sub_id, const char* client_ip) {
    if (!g_db || !event_id || !sub_id || !client_ip) return;
    
    const char* sql =
        "INSERT INTO event_broadcasts (event_id, subscription_id, client_ip) "
        "VALUES (?, ?, ?)";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 2, sub_id, -1, SQLITE_STATIC);
        sqlite3_bind_text(stmt, 3, client_ip, -1, SQLITE_STATIC);
        
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    }
}

// Update events sent counter for a subscription
void update_subscription_events_sent(const char* sub_id, int events_sent) {
    if (!g_db || !sub_id) return;
    
    const char* sql =
        "UPDATE subscription_events "
        "SET events_sent = ? "
        "WHERE subscription_id = ? AND event_type = 'created'";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
        sqlite3_bind_int(stmt, 1, events_sent);
        sqlite3_bind_text(stmt, 2, sub_id, -1, SQLITE_STATIC);
        
        sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             LOGGING FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Logging functions
void log_info(const char* message) {
    printf(BLUE "[INFO]" RESET " %s\n", message);
    fflush(stdout);
}

void log_success(const char* message) {
    printf(GREEN "[SUCCESS]" RESET " %s\n", message);
    fflush(stdout);
}

void log_error(const char* message) {
    printf(RED "[ERROR]" RESET " %s\n", message);
    fflush(stdout);
}

void log_warning(const char* message) {
    printf(YELLOW "[WARNING]" RESET " %s\n", message);
    fflush(stdout);
}

// Update subscription manager configuration from config system
void update_subscription_manager_config(void) {
    g_subscription_manager.max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", MAX_SUBSCRIPTIONS_PER_CLIENT);
    g_subscription_manager.max_total_subscriptions = get_config_int("max_total_subscriptions", MAX_TOTAL_SUBSCRIPTIONS);
    
    char config_msg[256];
    snprintf(config_msg, sizeof(config_msg),
             "Subscription limits: max_per_client=%d, max_total=%d",
             g_subscription_manager.max_subscriptions_per_client,
             g_subscription_manager.max_total_subscriptions);
    log_info(config_msg);
}

// Signal handler for graceful shutdown
void signal_handler(int sig) {
    if (sig == SIGINT || sig == SIGTERM) {
        log_info("Received shutdown signal");
        g_server_running = 0;
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             NOTICE MESSAGE SUPPORT
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Send NOTICE message to client (NIP-01)
void send_notice_message(struct lws* wsi, const char* message) {
    if (!wsi || !message) return;
    
    cJSON* notice_msg = cJSON_CreateArray();
    cJSON_AddItemToArray(notice_msg, cJSON_CreateString("NOTICE"));
    cJSON_AddItemToArray(notice_msg, cJSON_CreateString(message));
    
    char* msg_str = cJSON_Print(notice_msg);
    if (msg_str) {
        size_t msg_len = strlen(msg_str);
        unsigned char* buf = malloc(LWS_PRE + msg_len);
        if (buf) {
            memcpy(buf + LWS_PRE, msg_str, msg_len);
            lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
            free(buf);
        }
        free(msg_str);
    }
    
    cJSON_Delete(notice_msg);
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             NIP-09 EVENT DELETION REQUEST HANDLING
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Handle NIP-09 deletion request event (kind 5)
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size) {
    if (!event) {
        snprintf(error_message, error_size, "invalid: null deletion request");
        return -1;
    }
    
    // Extract event details
    cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
    cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
    cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
    cJSON* content_obj = cJSON_GetObjectItem(event, "content");
    cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
    
    if (!kind_obj || !pubkey_obj || !created_at_obj || !tags_obj || !event_id_obj) {
        snprintf(error_message, error_size, "invalid: incomplete deletion request");
        return -1;
    }
    
    int kind = (int)cJSON_GetNumberValue(kind_obj);
    if (kind != 5) {
        snprintf(error_message, error_size, "invalid: not a deletion request");
        return -1;
    }
    
    const char* requester_pubkey = cJSON_GetStringValue(pubkey_obj);
    // Extract deletion event ID and reason (for potential logging)
    const char* deletion_event_id = cJSON_GetStringValue(event_id_obj);
    const char* reason = content_obj ? cJSON_GetStringValue(content_obj) : "";
    (void)deletion_event_id; // Mark as intentionally unused for now
    (void)reason;           // Mark as intentionally unused for now
    long deletion_timestamp = (long)cJSON_GetNumberValue(created_at_obj);
    
    if (!cJSON_IsArray(tags_obj)) {
        snprintf(error_message, error_size, "invalid: deletion request tags must be an array");
        return -1;
    }
    
    // Collect event IDs and addresses from tags
    cJSON* event_ids = cJSON_CreateArray();
    cJSON* addresses = cJSON_CreateArray();
    cJSON* kinds_to_delete = cJSON_CreateArray();
    
    int deletion_targets_found = 0;
    
    cJSON* tag = NULL;
    cJSON_ArrayForEach(tag, tags_obj) {
        if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
            continue;
        }
        
        cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
        cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
        
        if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
            continue;
        }
        
        const char* name = cJSON_GetStringValue(tag_name);
        const char* value = cJSON_GetStringValue(tag_value);
        
        if (strcmp(name, "e") == 0) {
            // Event ID reference
            cJSON_AddItemToArray(event_ids, cJSON_CreateString(value));
            deletion_targets_found++;
        } else if (strcmp(name, "a") == 0) {
            // Addressable event reference (kind:pubkey:d-identifier)
            cJSON_AddItemToArray(addresses, cJSON_CreateString(value));
            deletion_targets_found++;
        } else if (strcmp(name, "k") == 0) {
            // Kind hint - store for validation but not required
            int kind_hint = atoi(value);
            if (kind_hint > 0) {
                cJSON_AddItemToArray(kinds_to_delete, cJSON_CreateNumber(kind_hint));
            }
        }
    }
    
    if (deletion_targets_found == 0) {
        cJSON_Delete(event_ids);
        cJSON_Delete(addresses);
        cJSON_Delete(kinds_to_delete);
        snprintf(error_message, error_size, "invalid: deletion request must contain 'e' or 'a' tags");
        return -1;
    }
    
    int deleted_count = 0;
    
    // Process event ID deletions
    if (cJSON_GetArraySize(event_ids) > 0) {
        int result = delete_events_by_id(requester_pubkey, event_ids);
        if (result > 0) {
            deleted_count += result;
        }
    }
    
    // Process addressable event deletions
    if (cJSON_GetArraySize(addresses) > 0) {
        int result = delete_events_by_address(requester_pubkey, addresses, deletion_timestamp);
        if (result > 0) {
            deleted_count += result;
        }
    }
    
    // Clean up
    cJSON_Delete(event_ids);
    cJSON_Delete(addresses);
    cJSON_Delete(kinds_to_delete);
    
    // Store the deletion request itself (it should be kept according to NIP-09)
    if (store_event(event) != 0) {
        log_warning("Failed to store deletion request event");
    }
    
    char debug_msg[256];
    snprintf(debug_msg, sizeof(debug_msg), "Deletion request processed: %d events deleted", deleted_count);
    log_info(debug_msg);
    
    error_message[0] = '\0'; // Success - empty error message
    return 0;
}

// Delete events by ID (with pubkey authorization)
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
    if (!g_db || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
        return 0;
    }
    
    int deleted_count = 0;
    
    cJSON* event_id = NULL;
    cJSON_ArrayForEach(event_id, event_ids) {
        if (!cJSON_IsString(event_id)) {
            continue;
        }
        
        const char* id = cJSON_GetStringValue(event_id);
        
        // First check if event exists and if requester is authorized
        const char* check_sql = "SELECT pubkey FROM events WHERE id = ?";
        sqlite3_stmt* check_stmt;
        
        int rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
        if (rc != SQLITE_OK) {
            continue;
        }
        
        sqlite3_bind_text(check_stmt, 1, id, -1, SQLITE_STATIC);
        
        if (sqlite3_step(check_stmt) == SQLITE_ROW) {
            const char* event_pubkey = (char*)sqlite3_column_text(check_stmt, 0);
            
            // Only delete if the requester is the author
            if (event_pubkey && strcmp(event_pubkey, requester_pubkey) == 0) {
                sqlite3_finalize(check_stmt);
                
                // Delete the event
                const char* delete_sql = "DELETE FROM events WHERE id = ? AND pubkey = ?";
                sqlite3_stmt* delete_stmt;
                
                rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
                if (rc == SQLITE_OK) {
                    sqlite3_bind_text(delete_stmt, 1, id, -1, SQLITE_STATIC);
                    sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
                    
                    if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
                        deleted_count++;
                        
                        char debug_msg[128];
                        snprintf(debug_msg, sizeof(debug_msg), "Deleted event by ID: %.16s...", id);
                        log_info(debug_msg);
                    }
                    sqlite3_finalize(delete_stmt);
                }
            } else {
                sqlite3_finalize(check_stmt);
                char warning_msg[128];
                snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
                log_warning(warning_msg);
            }
        } else {
            sqlite3_finalize(check_stmt);
            char debug_msg[128];
            snprintf(debug_msg, sizeof(debug_msg), "Event not found for deletion: %.16s...", id);
            log_info(debug_msg);
        }
    }
    
    return deleted_count;
}

// Delete events by addressable reference (kind:pubkey:d-identifier)
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp) {
    if (!g_db || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
        return 0;
    }
    
    int deleted_count = 0;
    
    cJSON* address = NULL;
    cJSON_ArrayForEach(address, addresses) {
        if (!cJSON_IsString(address)) {
            continue;
        }
        
        const char* addr = cJSON_GetStringValue(address);
        
        // Parse address format: kind:pubkey:d-identifier
        char* addr_copy = strdup(addr);
        if (!addr_copy) continue;
        
        char* kind_str = strtok(addr_copy, ":");
        char* pubkey_str = strtok(NULL, ":");
        char* d_identifier = strtok(NULL, ":");
        
        if (!kind_str || !pubkey_str) {
            free(addr_copy);
            continue;
        }
        
        int kind = atoi(kind_str);
        
        // Only delete if the requester is the author
        if (strcmp(pubkey_str, requester_pubkey) != 0) {
            free(addr_copy);
            char warning_msg[128];
            snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for address: %.32s...", addr);
            log_warning(warning_msg);
            continue;
        }
        
        // Build deletion query based on whether we have d-identifier
        const char* delete_sql;
        sqlite3_stmt* delete_stmt;
        
        if (d_identifier && strlen(d_identifier) > 0) {
            // Delete specific addressable event with d-tag
            delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ? "
                        "AND json_extract(tags, '$[*]') LIKE '%[\"d\",\"' || ? || '\"]%'";
        } else {
            // Delete all events of this kind by this author up to deletion timestamp
            delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ?";
        }
        
        int rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
        if (rc == SQLITE_OK) {
            sqlite3_bind_int(delete_stmt, 1, kind);
            sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
            sqlite3_bind_int64(delete_stmt, 3, deletion_timestamp);
            
            if (d_identifier && strlen(d_identifier) > 0) {
                sqlite3_bind_text(delete_stmt, 4, d_identifier, -1, SQLITE_STATIC);
            }
            
            if (sqlite3_step(delete_stmt) == SQLITE_DONE) {
                int changes = sqlite3_changes(g_db);
                if (changes > 0) {
                    deleted_count += changes;
                    
                    char debug_msg[128];
                    snprintf(debug_msg, sizeof(debug_msg), "Deleted %d events by address: %.32s...", changes, addr);
                    log_info(debug_msg);
                }
            }
            sqlite3_finalize(delete_stmt);
        }
        
        free(addr_copy);
    }
    
    return deleted_count;
}

// Mark event as deleted (alternative to hard deletion - not used in current implementation)
int mark_event_as_deleted(const char* event_id, const char* deletion_event_id, const char* reason) {
    (void)event_id; (void)deletion_event_id; (void)reason; // Suppress unused warnings
    
    // This function could be used if we wanted to implement soft deletion
    // For now, NIP-09 implementation uses hard deletion as specified
    
    return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             NIP-11 RELAY INFORMATION DOCUMENT
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Initialize relay information using configuration system
void init_relay_info() {
    // Load relay information from configuration system
    const char* relay_name = get_config_value("relay_name");
    if (relay_name) {
        strncpy(g_relay_info.name, relay_name, sizeof(g_relay_info.name) - 1);
    } else {
        strncpy(g_relay_info.name, "C Nostr Relay", sizeof(g_relay_info.name) - 1);
    }
    
    const char* relay_description = get_config_value("relay_description");
    if (relay_description) {
        strncpy(g_relay_info.description, relay_description, sizeof(g_relay_info.description) - 1);
    } else {
        strncpy(g_relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_relay_info.description) - 1);
    }
    
    const char* relay_software = get_config_value("relay_software");
    if (relay_software) {
        strncpy(g_relay_info.software, relay_software, sizeof(g_relay_info.software) - 1);
    } else {
        strncpy(g_relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_relay_info.software) - 1);
    }
    
    const char* relay_version = get_config_value("relay_version");
    if (relay_version) {
        strncpy(g_relay_info.version, relay_version, sizeof(g_relay_info.version) - 1);
    } else {
        strncpy(g_relay_info.version, "0.2.0", sizeof(g_relay_info.version) - 1);
    }
    
    // Load optional fields
    const char* relay_contact = get_config_value("relay_contact");
    if (relay_contact) {
        strncpy(g_relay_info.contact, relay_contact, sizeof(g_relay_info.contact) - 1);
    }
    
    const char* relay_pubkey = get_config_value("relay_pubkey");
    if (relay_pubkey) {
        strncpy(g_relay_info.pubkey, relay_pubkey, sizeof(g_relay_info.pubkey) - 1);
    }
    
    // Initialize supported NIPs array
    g_relay_info.supported_nips = cJSON_CreateArray();
    if (g_relay_info.supported_nips) {
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1));   // NIP-01: Basic protocol
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9));   // NIP-09: Event deletion
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11));  // NIP-11: Relay information
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(13));  // NIP-13: Proof of Work
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15));  // NIP-15: EOSE
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20));  // NIP-20: Command results
        cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(40));  // NIP-40: Expiration Timestamp
    }
    
    // Initialize server limitations using configuration
    g_relay_info.limitation = cJSON_CreateObject();
    if (g_relay_info.limitation) {
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_message_length", get_config_int("max_message_length", 16384));
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_subscriptions", get_config_int("max_subscriptions_per_client", 20));
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_limit", get_config_int("max_limit", 5000));
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", get_config_int("max_event_tags", 100));
        cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", get_config_int("max_content_length", 8196));
        cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
        cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", get_config_bool("admin_enabled", 0) ? cJSON_True : cJSON_False);
        cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
        cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
        cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_lower_limit", 0);
        cJSON_AddNumberToObject(g_relay_info.limitation, "created_at_upper_limit", 2147483647);
        cJSON_AddNumberToObject(g_relay_info.limitation, "default_limit", get_config_int("default_limit", 500));
    }
    
    // Initialize empty retention policies (can be configured later)
    g_relay_info.retention = cJSON_CreateArray();
    
    // Initialize language tags - set to global for now
    g_relay_info.language_tags = cJSON_CreateArray();
    if (g_relay_info.language_tags) {
        cJSON_AddItemToArray(g_relay_info.language_tags, cJSON_CreateString("*"));
    }
    
    // Initialize relay countries - set to global for now
    g_relay_info.relay_countries = cJSON_CreateArray();
    if (g_relay_info.relay_countries) {
        cJSON_AddItemToArray(g_relay_info.relay_countries, cJSON_CreateString("*"));
    }
    
    // Initialize content tags as empty array
    g_relay_info.tags = cJSON_CreateArray();
    
    // Initialize fees as empty object (no payment required by default)
    g_relay_info.fees = cJSON_CreateObject();
    
    log_success("Relay information initialized with default values");
}

// Clean up relay information JSON objects
void cleanup_relay_info() {
    if (g_relay_info.supported_nips) {
        cJSON_Delete(g_relay_info.supported_nips);
        g_relay_info.supported_nips = NULL;
    }
    if (g_relay_info.limitation) {
        cJSON_Delete(g_relay_info.limitation);
        g_relay_info.limitation = NULL;
    }
    if (g_relay_info.retention) {
        cJSON_Delete(g_relay_info.retention);
        g_relay_info.retention = NULL;
    }
    if (g_relay_info.language_tags) {
        cJSON_Delete(g_relay_info.language_tags);
        g_relay_info.language_tags = NULL;
    }
    if (g_relay_info.relay_countries) {
        cJSON_Delete(g_relay_info.relay_countries);
        g_relay_info.relay_countries = NULL;
    }
    if (g_relay_info.tags) {
        cJSON_Delete(g_relay_info.tags);
        g_relay_info.tags = NULL;
    }
    if (g_relay_info.fees) {
        cJSON_Delete(g_relay_info.fees);
        g_relay_info.fees = NULL;
    }
}

// Generate NIP-11 compliant JSON document
cJSON* generate_relay_info_json() {
    cJSON* info = cJSON_CreateObject();
    if (!info) {
        log_error("Failed to create relay info JSON object");
        return NULL;
    }
    
    // Add basic relay information
    if (strlen(g_relay_info.name) > 0) {
        cJSON_AddStringToObject(info, "name", g_relay_info.name);
    }
    if (strlen(g_relay_info.description) > 0) {
        cJSON_AddStringToObject(info, "description", g_relay_info.description);
    }
    if (strlen(g_relay_info.banner) > 0) {
        cJSON_AddStringToObject(info, "banner", g_relay_info.banner);
    }
    if (strlen(g_relay_info.icon) > 0) {
        cJSON_AddStringToObject(info, "icon", g_relay_info.icon);
    }
    if (strlen(g_relay_info.pubkey) > 0) {
        cJSON_AddStringToObject(info, "pubkey", g_relay_info.pubkey);
    }
    if (strlen(g_relay_info.contact) > 0) {
        cJSON_AddStringToObject(info, "contact", g_relay_info.contact);
    }
    
    // Add supported NIPs
    if (g_relay_info.supported_nips) {
        cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_relay_info.supported_nips, 1));
    }
    
    // Add software information
    if (strlen(g_relay_info.software) > 0) {
        cJSON_AddStringToObject(info, "software", g_relay_info.software);
    }
    if (strlen(g_relay_info.version) > 0) {
        cJSON_AddStringToObject(info, "version", g_relay_info.version);
    }
    
    // Add policies
    if (strlen(g_relay_info.privacy_policy) > 0) {
        cJSON_AddStringToObject(info, "privacy_policy", g_relay_info.privacy_policy);
    }
    if (strlen(g_relay_info.terms_of_service) > 0) {
        cJSON_AddStringToObject(info, "terms_of_service", g_relay_info.terms_of_service);
    }
    if (strlen(g_relay_info.posting_policy) > 0) {
        cJSON_AddStringToObject(info, "posting_policy", g_relay_info.posting_policy);
    }
    
    // Add server limitations
    if (g_relay_info.limitation) {
        cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_relay_info.limitation, 1));
    }
    
    // Add retention policies if configured
    if (g_relay_info.retention && cJSON_GetArraySize(g_relay_info.retention) > 0) {
        cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_relay_info.retention, 1));
    }
    
    // Add geographical and language information
    if (g_relay_info.relay_countries) {
        cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_relay_info.relay_countries, 1));
    }
    if (g_relay_info.language_tags) {
        cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_relay_info.language_tags, 1));
    }
    if (g_relay_info.tags && cJSON_GetArraySize(g_relay_info.tags) > 0) {
        cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_relay_info.tags, 1));
    }
    
    // Add payment information if configured
    if (strlen(g_relay_info.payments_url) > 0) {
        cJSON_AddStringToObject(info, "payments_url", g_relay_info.payments_url);
    }
    if (g_relay_info.fees && cJSON_GetObjectItem(g_relay_info.fees, "admission")) {
        cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_relay_info.fees, 1));
    }
    
    return info;
}

// Handle NIP-11 HTTP request
int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
    log_info("Handling NIP-11 relay information request");
    
    // Check if client accepts application/nostr+json
    int accepts_nostr_json = 0;
    if (accept_header) {
        if (strstr(accept_header, "application/nostr+json") != NULL) {
            accepts_nostr_json = 1;
        }
    }
    
    if (!accepts_nostr_json) {
        log_warning("HTTP request without proper Accept header for NIP-11");
        // Return 406 Not Acceptable
        unsigned char buf[LWS_PRE + 256];
        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_NOT_ACCEPTABLE, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_content_length(wsi, 0, &p, end)) {
            return -1;
        }
        if (lws_finalize_http_header(wsi, &p, end)) {
            return -1;
        }
        lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
        return -1; // Close connection
    }
    
    // Generate relay information JSON
    cJSON* info_json = generate_relay_info_json();
    if (!info_json) {
        log_error("Failed to generate relay info JSON");
        unsigned char buf[LWS_PRE + 256];
        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_INTERNAL_SERVER_ERROR, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_content_length(wsi, 0, &p, end)) {
            return -1;
        }
        if (lws_finalize_http_header(wsi, &p, end)) {
            return -1;
        }
        lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
        return -1;
    }
    
    char* json_string = cJSON_Print(info_json);
    cJSON_Delete(info_json);
    
    if (!json_string) {
        log_error("Failed to serialize relay info JSON");
        unsigned char buf[LWS_PRE + 256];
        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_INTERNAL_SERVER_ERROR, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) {
            return -1;
        }
        if (lws_add_http_header_content_length(wsi, 0, &p, end)) {
            return -1;
        }
        if (lws_finalize_http_header(wsi, &p, end)) {
            return -1;
        }
        lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
        return -1;
    }
    
    size_t json_len = strlen(json_string);
    
    // Prepare HTTP response with CORS headers
    unsigned char buf[LWS_PRE + 1024];
    unsigned char *p = &buf[LWS_PRE];
    unsigned char *start = p;
    unsigned char *end = &buf[sizeof(buf) - 1];
    
    // Add status
    if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) {
        free(json_string);
        return -1;
    }
    
    // Add content type
    if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
                                     (unsigned char*)"application/nostr+json", 22, &p, end)) {
        free(json_string);
        return -1;
    }
    
    // Add content length
    if (lws_add_http_header_content_length(wsi, json_len, &p, end)) {
        free(json_string);
        return -1;
    }
    
    // Add CORS headers as required by NIP-11
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-origin:",
                                    (unsigned char*)"*", 1, &p, end)) {
        free(json_string);
        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(json_string);
        return -1;
    }
    if (lws_add_http_header_by_name(wsi, (unsigned char*)"access-control-allow-methods:",
                                    (unsigned char*)"GET, OPTIONS", 12, &p, end)) {
        free(json_string);
        return -1;
    }
    
    // Finalize headers
    if (lws_finalize_http_header(wsi, &p, end)) {
        free(json_string);
        return -1;
    }
    
    // Write headers
    if (lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS) < 0) {
        free(json_string);
        return -1;
    }
    
    // Write JSON body
    unsigned char *json_buf = malloc(LWS_PRE + json_len);
    if (!json_buf) {
        free(json_string);
        return -1;
    }
    
    memcpy(json_buf + LWS_PRE, json_string, json_len);
    if (lws_write(wsi, json_buf + LWS_PRE, json_len, LWS_WRITE_HTTP) < 0) {
        free(json_string);
        free(json_buf);
        return -1;
    }
    
    free(json_buf);
    
    free(json_string);
    log_success("NIP-11 relay information served successfully");
    return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             NIP-13 PROOF OF WORK VALIDATION
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Initialize PoW configuration using configuration system
void init_pow_config() {
    log_info("Initializing NIP-13 Proof of Work configuration");
    
    // Load PoW settings from configuration system
    g_pow_config.enabled = get_config_bool("pow_enabled", 1);
    g_pow_config.min_pow_difficulty = get_config_int("pow_min_difficulty", 0);
    
    // Get PoW mode from configuration
    const char* pow_mode = get_config_value("pow_mode");
    if (pow_mode) {
        if (strcmp(pow_mode, "strict") == 0) {
            g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
            g_pow_config.require_nonce_tag = 1;
            g_pow_config.reject_lower_targets = 1;
            g_pow_config.strict_format = 1;
            g_pow_config.anti_spam_mode = 1;
            log_info("PoW configured in strict anti-spam mode");
        } else if (strcmp(pow_mode, "full") == 0) {
            g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
            g_pow_config.require_nonce_tag = 1;
            log_info("PoW configured in full validation mode");
        } else if (strcmp(pow_mode, "basic") == 0) {
            g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
            log_info("PoW configured in basic validation mode");
        } else if (strcmp(pow_mode, "disabled") == 0) {
            g_pow_config.enabled = 0;
            log_info("PoW validation disabled via configuration");
        }
    } else {
        // Default to basic mode
        g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
        log_info("PoW configured in basic validation mode (default)");
    }
    
    // Log final configuration
    char config_msg[512];
    snprintf(config_msg, sizeof(config_msg),
             "PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
             g_pow_config.enabled ? "true" : "false",
             g_pow_config.min_pow_difficulty,
             g_pow_config.validation_flags,
             g_pow_config.anti_spam_mode ? "anti-spam" :
             (g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
    log_info(config_msg);
}

// Validate event Proof of Work according to NIP-13
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
    if (!g_pow_config.enabled) {
        return 0; // PoW validation disabled
    }
    
    if (!event) {
        snprintf(error_message, error_size, "pow: null event");
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // If min_pow_difficulty is 0, only validate events that have nonce tags
    // This allows events without PoW when difficulty requirement is 0
    if (g_pow_config.min_pow_difficulty == 0) {
        cJSON* tags = cJSON_GetObjectItem(event, "tags");
        int has_nonce_tag = 0;
        
        if (tags && cJSON_IsArray(tags)) {
            cJSON* tag = NULL;
            cJSON_ArrayForEach(tag, tags) {
                if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
                    cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
                    if (cJSON_IsString(tag_name)) {
                        const char* name = cJSON_GetStringValue(tag_name);
                        if (name && strcmp(name, "nonce") == 0) {
                            has_nonce_tag = 1;
                            break;
                        }
                    }
                }
            }
        }
        
        // If no minimum difficulty required and no nonce tag, skip PoW validation
        if (!has_nonce_tag) {
            return 0; // Accept event without PoW when min_difficulty=0
        }
    }
    
    // Perform PoW validation using nostr_core_lib
    nostr_pow_result_t pow_result;
    int validation_result = nostr_validate_pow(event, g_pow_config.min_pow_difficulty,
                                             g_pow_config.validation_flags, &pow_result);
    
    if (validation_result != NOSTR_SUCCESS) {
        // Handle specific error cases with appropriate messages
        switch (validation_result) {
            case NOSTR_ERROR_NIP13_INSUFFICIENT:
                snprintf(error_message, error_size,
                        "pow: insufficient difficulty: %d < %d",
                        pow_result.actual_difficulty, g_pow_config.min_pow_difficulty);
                log_warning("Event rejected: insufficient PoW difficulty");
                break;
            case NOSTR_ERROR_NIP13_NO_NONCE_TAG:
                // This should not happen with min_difficulty=0 after our check above
                if (g_pow_config.min_pow_difficulty > 0) {
                    snprintf(error_message, error_size, "pow: missing required nonce tag");
                    log_warning("Event rejected: missing nonce tag");
                } else {
                    return 0; // Allow when min_difficulty=0
                }
                break;
            case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG:
                snprintf(error_message, error_size, "pow: invalid nonce tag format");
                log_warning("Event rejected: invalid nonce tag format");
                break;
            case NOSTR_ERROR_NIP13_TARGET_MISMATCH:
                snprintf(error_message, error_size,
                        "pow: committed target (%d) lower than minimum (%d)",
                        pow_result.committed_target, g_pow_config.min_pow_difficulty);
                log_warning("Event rejected: committed target too low (anti-spam protection)");
                break;
            case NOSTR_ERROR_NIP13_CALCULATION:
                snprintf(error_message, error_size, "pow: difficulty calculation failed");
                log_error("PoW difficulty calculation error");
                break;
            case NOSTR_ERROR_EVENT_INVALID_ID:
                snprintf(error_message, error_size, "pow: invalid event ID format");
                log_warning("Event rejected: invalid event ID for PoW calculation");
                break;
            default:
                snprintf(error_message, error_size, "pow: validation failed - %s",
                        strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error");
                log_warning("Event rejected: PoW validation failed");
        }
        return validation_result;
    }
    
    // Log successful PoW validation (only if minimum difficulty is required)
    if (g_pow_config.min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
        char debug_msg[256];
        snprintf(debug_msg, sizeof(debug_msg),
                "PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
                pow_result.actual_difficulty,
                pow_result.committed_target,
                (unsigned long long)pow_result.nonce_value,
                pow_result.has_nonce_tag ? "" : " (no nonce tag)");
        log_info(debug_msg);
    }
    
    return 0;  // Success
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             NIP-40 EXPIRATION TIMESTAMP HANDLING
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Initialize expiration configuration using configuration system
void init_expiration_config() {
    log_info("Initializing NIP-40 Expiration Timestamp configuration");
    
    // Load expiration settings from configuration system
    g_expiration_config.enabled = get_config_bool("expiration_enabled", 1);
    g_expiration_config.strict_mode = get_config_bool("expiration_strict", 1);
    g_expiration_config.filter_responses = get_config_bool("expiration_filter", 1);
    g_expiration_config.delete_expired = get_config_bool("expiration_delete", 0);
    g_expiration_config.grace_period = get_config_int("expiration_grace_period", 300);
    
    // Validate grace period bounds
    if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
        log_warning("Invalid grace period, using default of 300 seconds");
        g_expiration_config.grace_period = 300;
    }
    
    // Log final configuration
    char config_msg[512];
    snprintf(config_msg, sizeof(config_msg),
             "Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
             g_expiration_config.enabled ? "true" : "false",
             g_expiration_config.strict_mode ? "true" : "false",
             g_expiration_config.filter_responses ? "true" : "false",
             g_expiration_config.grace_period);
    log_info(config_msg);
}

// Extract expiration timestamp from event tags
long extract_expiration_timestamp(cJSON* tags) {
    if (!tags || !cJSON_IsArray(tags)) {
        return 0; // No expiration
    }
    
    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 (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
                const char* name = cJSON_GetStringValue(tag_name);
                const char* value = cJSON_GetStringValue(tag_value);
                
                if (name && value && strcmp(name, "expiration") == 0) {
                    long expiration_ts = atol(value);
                    if (expiration_ts > 0) {
                        return expiration_ts;
                    }
                }
            }
        }
    }
    
    return 0; // No valid expiration tag found
}

// Check if event is currently expired
int is_event_expired(cJSON* event, time_t current_time) {
    if (!event) {
        return 0; // Invalid event, not expired
    }
    
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    long expiration_ts = extract_expiration_timestamp(tags);
    
    if (expiration_ts == 0) {
        return 0; // No expiration timestamp, not expired
    }
    
    // Check if current time exceeds expiration + grace period
    return (current_time > (expiration_ts + g_expiration_config.grace_period));
}

// Validate event expiration according to NIP-40
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) {
    if (!g_expiration_config.enabled) {
        return 0; // Expiration validation disabled
    }
    
    if (!event) {
        snprintf(error_message, error_size, "expiration: null event");
        return -1;
    }
    
    // Check if event is expired
    time_t current_time = time(NULL);
    if (is_event_expired(event, current_time)) {
        if (g_expiration_config.strict_mode) {
            cJSON* tags = cJSON_GetObjectItem(event, "tags");
            long expiration_ts = extract_expiration_timestamp(tags);
            
            snprintf(error_message, error_size,
                    "invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
                    expiration_ts, (long)current_time, g_expiration_config.grace_period);
            log_warning("Event rejected: expired timestamp");
            return -1;
        } else {
            // In non-strict mode, log but allow expired events
            char debug_msg[256];
            snprintf(debug_msg, sizeof(debug_msg),
                    "Accepting expired event (strict_mode disabled)");
            log_info(debug_msg);
        }
    }
    
    return 0; // Success
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             DATABASE FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Initialize database connection
int init_database() {
    // Use configurable database path, falling back to default
    const char* db_path = get_config_value("database_path");
    if (!db_path) {
        db_path = DEFAULT_DATABASE_PATH;
    }
    
    int rc = sqlite3_open(db_path, &g_db);
    if (rc != SQLITE_OK) {
        log_error("Cannot open database");
        return -1;
    }
    
    char success_msg[256];
    snprintf(success_msg, sizeof(success_msg), "Database connection established: %s", db_path);
    log_success(success_msg);
    return 0;
}

// Close database connection
void close_database() {
    if (g_db) {
        sqlite3_close(g_db);
        g_db = NULL;
        log_info("Database connection closed");
    }
}

// Event type classification
typedef enum {
    EVENT_TYPE_REGULAR,
    EVENT_TYPE_REPLACEABLE,
    EVENT_TYPE_EPHEMERAL,
    EVENT_TYPE_ADDRESSABLE,
    EVENT_TYPE_UNKNOWN
} event_type_t;

event_type_t classify_event_kind(int kind) {
    if ((kind >= 1000 && kind < 10000) ||
        (kind >= 4 && kind < 45) ||
        kind == 1 || kind == 2) {
        return EVENT_TYPE_REGULAR;
    }
    if ((kind >= 10000 && kind < 20000) ||
        kind == 0 || kind == 3) {
        return EVENT_TYPE_REPLACEABLE;
    }
    if (kind >= 20000 && kind < 30000) {
        return EVENT_TYPE_EPHEMERAL;
    }
    if (kind >= 30000 && kind < 40000) {
        return EVENT_TYPE_ADDRESSABLE;
    }
    return EVENT_TYPE_UNKNOWN;
}

const char* event_type_to_string(event_type_t type) {
    switch (type) {
        case EVENT_TYPE_REGULAR: return "regular";
        case EVENT_TYPE_REPLACEABLE: return "replaceable";
        case EVENT_TYPE_EPHEMERAL: return "ephemeral";
        case EVENT_TYPE_ADDRESSABLE: return "addressable";
        default: return "unknown";
    }
}

// Helper function to extract d tag value from tags array
const char* extract_d_tag_value(cJSON* tags) {
    if (!tags || !cJSON_IsArray(tags)) {
        return NULL;
    }
    
    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 (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
                const char* name = cJSON_GetStringValue(tag_name);
                if (name && strcmp(name, "d") == 0) {
                    return cJSON_GetStringValue(tag_value);
                }
            }
        }
    }
    
    return NULL;
}

// Check and handle replaceable events according to NIP-01
int check_and_handle_replaceable_event(int kind, const char* pubkey, long created_at) {
    if (!g_db || !pubkey) return 0;
    
    const char* sql =
        "SELECT created_at FROM events WHERE kind = ? AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        return 0; // Allow storage on DB error
    }
    
    sqlite3_bind_int(stmt, 1, kind);
    sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_STATIC);
    
    int result = 0;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        long existing_created_at = sqlite3_column_int64(stmt, 0);
        if (created_at <= existing_created_at) {
            result = -1; // Older or same timestamp, reject
        } else {
            // Delete older versions
            const char* delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at < ?";
            sqlite3_stmt* delete_stmt;
            if (sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL) == SQLITE_OK) {
                sqlite3_bind_int(delete_stmt, 1, kind);
                sqlite3_bind_text(delete_stmt, 2, pubkey, -1, SQLITE_STATIC);
                sqlite3_bind_int64(delete_stmt, 3, created_at);
                sqlite3_step(delete_stmt);
                sqlite3_finalize(delete_stmt);
            }
        }
    }
    
    sqlite3_finalize(stmt);
    return result;
}

// Check and handle addressable events according to NIP-01
int check_and_handle_addressable_event(int kind, const char* pubkey, const char* d_tag_value, long created_at) {
    if (!g_db || !pubkey) return 0;
    
    // If no d tag, treat as regular replaceable
    if (!d_tag_value) {
        return check_and_handle_replaceable_event(kind, pubkey, created_at);
    }
    
    const char* sql =
        "SELECT created_at FROM events WHERE kind = ? AND pubkey = ? AND json_extract(tags, '$[*][1]') = ? "
        "AND json_extract(tags, '$[*][0]') = 'd' ORDER BY created_at DESC LIMIT 1";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        return 0; // Allow storage on DB error
    }
    
    sqlite3_bind_int(stmt, 1, kind);
    sqlite3_bind_text(stmt, 2, pubkey, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, d_tag_value, -1, SQLITE_STATIC);
    
    int result = 0;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        long existing_created_at = sqlite3_column_int64(stmt, 0);
        if (created_at <= existing_created_at) {
            result = -1; // Older or same timestamp, reject
        } else {
            // Delete older versions with same kind, pubkey, and d tag
            const char* delete_sql =
                "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at < ? "
                "AND json_extract(tags, '$[*][1]') = ? AND json_extract(tags, '$[*][0]') = 'd'";
            sqlite3_stmt* delete_stmt;
            if (sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL) == SQLITE_OK) {
                sqlite3_bind_int(delete_stmt, 1, kind);
                sqlite3_bind_text(delete_stmt, 2, pubkey, -1, SQLITE_STATIC);
                sqlite3_bind_int64(delete_stmt, 3, created_at);
                sqlite3_bind_text(delete_stmt, 4, d_tag_value, -1, SQLITE_STATIC);
                sqlite3_step(delete_stmt);
                sqlite3_finalize(delete_stmt);
            }
        }
    }
    
    sqlite3_finalize(stmt);
    return result;
}

// Store event in database
int store_event(cJSON* event) {
    if (!g_db || !event) {
        return -1;
    }
    
    // Extract event fields
    cJSON* id = cJSON_GetObjectItem(event, "id");
    cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
    cJSON* created_at = cJSON_GetObjectItem(event, "created_at");
    cJSON* kind = cJSON_GetObjectItem(event, "kind");
    cJSON* content = cJSON_GetObjectItem(event, "content");
    cJSON* sig = cJSON_GetObjectItem(event, "sig");
    cJSON* tags = cJSON_GetObjectItem(event, "tags");
    
    if (!id || !pubkey || !created_at || !kind || !content || !sig) {
        log_error("Invalid event - missing required fields");
        return -1;
    }
    
    // Classify event type
    event_type_t type = classify_event_kind((int)cJSON_GetNumberValue(kind));
    
    // Serialize tags to JSON (use empty array if no tags)
    char* tags_json = NULL;
    if (tags && cJSON_IsArray(tags)) {
        tags_json = cJSON_Print(tags);
    } else {
        tags_json = strdup("[]");
    }
    
    if (!tags_json) {
        log_error("Failed to serialize tags to JSON");
        return -1;
    }
    
    // Prepare SQL statement for event insertion
    const char* sql =
        "INSERT INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) "
        "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        log_error("Failed to prepare event insert statement");
        free(tags_json);
        return -1;
    }
    
    // Bind parameters
    sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey), -1, SQLITE_STATIC);
    sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at));
    sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind));
    sqlite3_bind_text(stmt, 5, event_type_to_string(type), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 8, tags_json, -1, SQLITE_TRANSIENT);
    
    // Execute statement
    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    
    if (rc != SQLITE_DONE) {
        if (rc == SQLITE_CONSTRAINT) {
            log_warning("Event already exists in database");
            free(tags_json);
            return 0; // Not an error, just duplicate
        }
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", sqlite3_errmsg(g_db));
        log_error(error_msg);
        free(tags_json);
        return -1;
    }
    
    free(tags_json);
    log_success("Event stored in database");
    return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             EVENT STORAGE AND RETRIEVAL
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

cJSON* retrieve_event(const char* event_id) {
    if (!g_db || !event_id) {
        return NULL;
    }
    
    const char* sql =
        "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE id = ?";
    
    sqlite3_stmt* stmt;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        return NULL;
    }
    
    sqlite3_bind_text(stmt, 1, event_id, -1, SQLITE_STATIC);
    
    cJSON* event = NULL;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        event = cJSON_CreateObject();
        
        cJSON_AddStringToObject(event, "id", (char*)sqlite3_column_text(stmt, 0));
        cJSON_AddStringToObject(event, "pubkey", (char*)sqlite3_column_text(stmt, 1));
        cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
        cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
        cJSON_AddStringToObject(event, "content", (char*)sqlite3_column_text(stmt, 4));
        cJSON_AddStringToObject(event, "sig", (char*)sqlite3_column_text(stmt, 5));
        
        // Parse tags JSON
        const char* tags_json = (char*)sqlite3_column_text(stmt, 6);
        if (tags_json) {
            cJSON* tags = cJSON_Parse(tags_json);
            if (tags) {
                cJSON_AddItemToObject(event, "tags", tags);
            } else {
                cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
            }
        } else {
            cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
        }
    }
    
    sqlite3_finalize(stmt);
    return event;
}


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             SUBSCRIPTION HANDLERS
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
    log_info("Handling REQ message for persistent subscription");
    
    if (!cJSON_IsArray(filters)) {
        log_error("REQ filters is not an array");
        return 0;
    }
    
    // Check session subscription limits
    if (pss && pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
        log_error("Maximum subscriptions per client exceeded");
        
        // Send CLOSED notice
        cJSON* closed_msg = cJSON_CreateArray();
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString("CLOSED"));
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString(sub_id));
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString("error: too many subscriptions"));
        
        char* closed_str = cJSON_Print(closed_msg);
        if (closed_str) {
            size_t closed_len = strlen(closed_str);
            unsigned char* buf = malloc(LWS_PRE + closed_len);
            if (buf) {
                memcpy(buf + LWS_PRE, closed_str, closed_len);
                lws_write(wsi, buf + LWS_PRE, closed_len, LWS_WRITE_TEXT);
                free(buf);
            }
            free(closed_str);
        }
        cJSON_Delete(closed_msg);
        
        return 0;
    }
    
    // Create persistent subscription
    subscription_t* subscription = create_subscription(sub_id, wsi, filters, pss ? pss->client_ip : "unknown");
    if (!subscription) {
        log_error("Failed to create subscription");
        return 0;
    }
    
    // Add to global manager
    if (add_subscription_to_manager(subscription) != 0) {
        log_error("Failed to add subscription to global manager");
        free_subscription(subscription);
        
        // Send CLOSED notice
        cJSON* closed_msg = cJSON_CreateArray();
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString("CLOSED"));
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString(sub_id));
        cJSON_AddItemToArray(closed_msg, cJSON_CreateString("error: subscription limit reached"));
        
        char* closed_str = cJSON_Print(closed_msg);
        if (closed_str) {
            size_t closed_len = strlen(closed_str);
            unsigned char* buf = malloc(LWS_PRE + closed_len);
            if (buf) {
                memcpy(buf + LWS_PRE, closed_str, closed_len);
                lws_write(wsi, buf + LWS_PRE, closed_len, LWS_WRITE_TEXT);
                free(buf);
            }
            free(closed_str);
        }
        cJSON_Delete(closed_msg);
        
        return 0;
    }
    
    // Add to session's subscription list (if session data available)
    if (pss) {
        pthread_mutex_lock(&pss->session_lock);
        subscription->session_next = pss->subscriptions;
        pss->subscriptions = subscription;
        pss->subscription_count++;
        pthread_mutex_unlock(&pss->session_lock);
    }
    
    int events_sent = 0;
    
    // Process each filter in the array
    for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
        cJSON* filter = cJSON_GetArrayItem(filters, i);
        if (!filter || !cJSON_IsObject(filter)) {
            log_warning("Invalid filter object");
            continue;
        }
        
        // Build SQL query based on filter
        char sql[1024] = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE 1=1";
        char* sql_ptr = sql + strlen(sql);
        int remaining = sizeof(sql) - strlen(sql);
        
        // Note: Expiration filtering will be done at application level
        // after retrieving events to ensure compatibility with all SQLite versions
        
        // Handle kinds filter
        cJSON* kinds = cJSON_GetObjectItem(filter, "kinds");
        if (kinds && cJSON_IsArray(kinds)) {
            int kind_count = cJSON_GetArraySize(kinds);
            if (kind_count > 0) {
                snprintf(sql_ptr, remaining, " AND kind IN (");
                sql_ptr += strlen(sql_ptr);
                remaining = sizeof(sql) - strlen(sql);
                
                for (int k = 0; k < kind_count; k++) {
                    cJSON* kind = cJSON_GetArrayItem(kinds, k);
                    if (cJSON_IsNumber(kind)) {
                        if (k > 0) {
                            snprintf(sql_ptr, remaining, ",");
                            sql_ptr++;
                            remaining--;
                        }
                        snprintf(sql_ptr, remaining, "%d", (int)cJSON_GetNumberValue(kind));
                        sql_ptr += strlen(sql_ptr);
                        remaining = sizeof(sql) - strlen(sql);
                    }
                }
                snprintf(sql_ptr, remaining, ")");
                sql_ptr += strlen(sql_ptr);
                remaining = sizeof(sql) - strlen(sql);
            }
        }
        
        // Handle authors filter
        cJSON* authors = cJSON_GetObjectItem(filter, "authors");
        if (authors && cJSON_IsArray(authors)) {
            int author_count = cJSON_GetArraySize(authors);
            if (author_count > 0) {
                snprintf(sql_ptr, remaining, " AND pubkey IN (");
                sql_ptr += strlen(sql_ptr);
                remaining = sizeof(sql) - strlen(sql);
                
                for (int a = 0; a < author_count; a++) {
                    cJSON* author = cJSON_GetArrayItem(authors, a);
                    if (cJSON_IsString(author)) {
                        if (a > 0) {
                            snprintf(sql_ptr, remaining, ",");
                            sql_ptr++;
                            remaining--;
                        }
                        snprintf(sql_ptr, remaining, "'%s'", cJSON_GetStringValue(author));
                        sql_ptr += strlen(sql_ptr);
                        remaining = sizeof(sql) - strlen(sql);
                    }
                }
                snprintf(sql_ptr, remaining, ")");
                sql_ptr += strlen(sql_ptr);
                remaining = sizeof(sql) - strlen(sql);
            }
        }
        
        // Handle since filter
        cJSON* since = cJSON_GetObjectItem(filter, "since");
        if (since && cJSON_IsNumber(since)) {
            snprintf(sql_ptr, remaining, " AND created_at >= %ld", (long)cJSON_GetNumberValue(since));
            sql_ptr += strlen(sql_ptr);
            remaining = sizeof(sql) - strlen(sql);
        }
        
        // Handle until filter
        cJSON* until = cJSON_GetObjectItem(filter, "until");
        if (until && cJSON_IsNumber(until)) {
            snprintf(sql_ptr, remaining, " AND created_at <= %ld", (long)cJSON_GetNumberValue(until));
            sql_ptr += strlen(sql_ptr);
            remaining = sizeof(sql) - strlen(sql);
        }
        
        // Add ordering and limit
        snprintf(sql_ptr, remaining, " ORDER BY created_at DESC");
        sql_ptr += strlen(sql_ptr);
        remaining = sizeof(sql) - strlen(sql);
        
        // Handle limit filter
        cJSON* limit = cJSON_GetObjectItem(filter, "limit");
        if (limit && cJSON_IsNumber(limit)) {
            int limit_val = (int)cJSON_GetNumberValue(limit);
            if (limit_val > 0 && limit_val <= 5000) {
                snprintf(sql_ptr, remaining, " LIMIT %d", limit_val);
            }
        } else {
            // Default limit to prevent excessive queries
            snprintf(sql_ptr, remaining, " LIMIT 500");
        }
        
        // Debug: Log the SQL query being executed
        char debug_msg[1280];
        snprintf(debug_msg, sizeof(debug_msg), "Executing SQL: %s", sql);
        log_info(debug_msg);
        
        // Execute query and send events
        sqlite3_stmt* stmt;
        int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
        if (rc != SQLITE_OK) {
            char error_msg[256];
            snprintf(error_msg, sizeof(error_msg), "Failed to prepare subscription query: %s", sqlite3_errmsg(g_db));
            log_error(error_msg);
            continue;
        }
        
        int row_count = 0;
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            row_count++;
            
            // Build event JSON
            cJSON* event = cJSON_CreateObject();
            cJSON_AddStringToObject(event, "id", (char*)sqlite3_column_text(stmt, 0));
            cJSON_AddStringToObject(event, "pubkey", (char*)sqlite3_column_text(stmt, 1));
            cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
            cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
            cJSON_AddStringToObject(event, "content", (char*)sqlite3_column_text(stmt, 4));
            cJSON_AddStringToObject(event, "sig", (char*)sqlite3_column_text(stmt, 5));
            
            // Parse tags JSON
            const char* tags_json = (char*)sqlite3_column_text(stmt, 6);
            cJSON* tags = NULL;
            if (tags_json) {
                tags = cJSON_Parse(tags_json);
            }
            if (!tags) {
                tags = cJSON_CreateArray();
            }
            cJSON_AddItemToObject(event, "tags", tags);
            
            // Check expiration filtering (NIP-40) at application level
            if (g_expiration_config.enabled && g_expiration_config.filter_responses) {
                time_t current_time = time(NULL);
                if (is_event_expired(event, current_time)) {
                    // Skip this expired event
                    cJSON_Delete(event);
                    continue;
                }
            }
            
            // Send EVENT message
            cJSON* event_msg = cJSON_CreateArray();
            cJSON_AddItemToArray(event_msg, cJSON_CreateString("EVENT"));
            cJSON_AddItemToArray(event_msg, cJSON_CreateString(sub_id));
            cJSON_AddItemToArray(event_msg, event);
            
            char* msg_str = cJSON_Print(event_msg);
            if (msg_str) {
                size_t msg_len = strlen(msg_str);
                unsigned char* buf = malloc(LWS_PRE + msg_len);
                if (buf) {
                    memcpy(buf + LWS_PRE, msg_str, msg_len);
                    lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
                    free(buf);
                }
                free(msg_str);
            }
            
            cJSON_Delete(event_msg);
            events_sent++;
        }
        
        char row_debug[128];
        snprintf(row_debug, sizeof(row_debug), "Query returned %d rows", row_count);
        log_info(row_debug);
        
        sqlite3_finalize(stmt);
    }
    
    char events_debug[128];
    snprintf(events_debug, sizeof(events_debug), "Total events sent: %d", events_sent);
    log_info(events_debug);
    
    return events_sent;
}

// Handle EVENT message (publish)
int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
    log_info("Handling EVENT message with full NIP-01 validation");
    
    if (!event) {
        snprintf(error_message, error_size, "invalid: null event");
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Step 1: Validate event structure
    int structure_result = nostr_validate_event_structure(event);
    if (structure_result != NOSTR_SUCCESS) {
        switch (structure_result) {
            case NOSTR_ERROR_EVENT_INVALID_STRUCTURE:
                snprintf(error_message, error_size, "invalid: malformed event structure");
                break;
            case NOSTR_ERROR_EVENT_INVALID_ID:
                snprintf(error_message, error_size, "invalid: invalid event id format");
                break;
            case NOSTR_ERROR_EVENT_INVALID_PUBKEY:
                snprintf(error_message, error_size, "invalid: invalid pubkey format");
                break;
            case NOSTR_ERROR_EVENT_INVALID_CREATED_AT:
                snprintf(error_message, error_size, "invalid: invalid created_at timestamp");
                break;
            case NOSTR_ERROR_EVENT_INVALID_KIND:
                snprintf(error_message, error_size, "invalid: invalid event kind");
                break;
            case NOSTR_ERROR_EVENT_INVALID_TAGS:
                snprintf(error_message, error_size, "invalid: invalid tags format");
                break;
            case NOSTR_ERROR_EVENT_INVALID_CONTENT:
                snprintf(error_message, error_size, "invalid: invalid content");
                break;
            default:
                snprintf(error_message, error_size, "invalid: event structure validation failed");
        }
        return structure_result;
    }
    
    // Step 2: Verify event signature
    int signature_result = nostr_verify_event_signature(event);
    if (signature_result != NOSTR_SUCCESS) {
        if (signature_result == NOSTR_ERROR_EVENT_INVALID_SIGNATURE) {
            snprintf(error_message, error_size, "invalid: event signature verification failed");
        } else if (signature_result == NOSTR_ERROR_EVENT_INVALID_ID) {
            snprintf(error_message, error_size, "invalid: event id does not match computed hash");
        } else {
            snprintf(error_message, error_size, "invalid: cryptographic validation failed");
        }
        return signature_result;
    }
    
    // Step 3: Validate Proof of Work (NIP-13) if enabled
    int pow_result = validate_event_pow(event, error_message, error_size);
    if (pow_result != 0) {
        return pow_result;  // PoW validation failed, error message already set
    }
    
    // Step 4: Validate expiration timestamp (NIP-40) if enabled
    int expiration_result = validate_event_expiration(event, error_message, error_size);
    if (expiration_result != 0) {
        return expiration_result;  // Expiration validation failed, error message already set
    }
    
    // Step 5: Complete event validation (combines structure + signature + additional checks)
    int validation_result = nostr_validate_event(event);
    if (validation_result != NOSTR_SUCCESS) {
        snprintf(error_message, error_size, "invalid: complete event validation failed");
        return validation_result;
    }
    
    // Step 6: Check for special event types and handle accordingly
    cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
    cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
    cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
    
    if (kind_obj && pubkey_obj && created_at_obj) {
        int kind = (int)cJSON_GetNumberValue(kind_obj);
        const char* pubkey = cJSON_GetStringValue(pubkey_obj);
        long created_at = (long)cJSON_GetNumberValue(created_at_obj);
        
        // NIP-09: Handle deletion requests (kind 5)
        if (kind == 5) {
            return handle_deletion_request(event, error_message, error_size);
        }
        
        // Handle replaceable events (NIP-01)
        event_type_t event_type = classify_event_kind(kind);
        if (event_type == EVENT_TYPE_REPLACEABLE) {
            // For replaceable events, check if we have a newer version
            if (check_and_handle_replaceable_event(kind, pubkey, created_at) < 0) {
                snprintf(error_message, error_size, "duplicate: older replaceable event ignored");
                return -2; // Special code for duplicate/older event
            }
        } else if (event_type == EVENT_TYPE_ADDRESSABLE) {
            // For addressable events, check d tag
            cJSON* tags = cJSON_GetObjectItem(event, "tags");
            if (tags && cJSON_IsArray(tags)) {
                const char* d_tag_value = extract_d_tag_value(tags);
                if (check_and_handle_addressable_event(kind, pubkey, d_tag_value, created_at) < 0) {
                    snprintf(error_message, error_size, "duplicate: older addressable event ignored");
                    return -2;
                }
            }
        } else if (event_type == EVENT_TYPE_EPHEMERAL) {
            // Ephemeral events should not be stored
            error_message[0] = '\0';  // Success but no storage - empty error message
            return 0;  // Accept but don't store
        }
    }
    
    // Step 7: Store event in database
    if (store_event(event) == 0) {
        error_message[0] = '\0';  // Success - empty error message
        log_success("Event validated and stored successfully");
        return 0;
    }
    
    snprintf(error_message, error_size, "error: failed to store event in database");
    return -1;
}



/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             WEBSOCKET PROTOCOL
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// WebSocket callback function for Nostr relay protocol
static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reason,
                               void *user, void *in, size_t len) {
    struct per_session_data *pss = (struct per_session_data *)user;
    
    switch (reason) {
        case LWS_CALLBACK_HTTP:
            // Handle NIP-11 relay information requests (HTTP GET to root path)
            {
                char *requested_uri = (char *)in;
                log_info("HTTP request received");
                
                // Check if this is a GET request to the root path
                if (strcmp(requested_uri, "/") == 0) {
                    // Get Accept header
                    char accept_header[256] = {0};
                    int header_len = lws_hdr_copy(wsi, accept_header, sizeof(accept_header) - 1, WSI_TOKEN_HTTP_ACCEPT);
                    
                    if (header_len > 0) {
                        accept_header[header_len] = '\0';
                        
                        // Handle NIP-11 request
                        if (handle_nip11_http_request(wsi, accept_header) == 0) {
                            return 0; // Successfully handled
                        }
                    } else {
                        log_warning("HTTP request without Accept header");
                    }
                    
                    // Return 404 for other requests
                    lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
                    return -1;
                }
                
                // Return 404 for non-root paths
                lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
                return -1;
            }
            
        case LWS_CALLBACK_HTTP_WRITEABLE:
            // HTTP response continuation if needed
            break;
            
        case LWS_CALLBACK_ESTABLISHED:
            log_info("WebSocket connection established");
            memset(pss, 0, sizeof(*pss));
            pthread_mutex_init(&pss->session_lock, NULL);
            // TODO: Get real client IP address
            strncpy(pss->client_ip, "127.0.0.1", CLIENT_IP_MAX_LENGTH - 1);
            pss->client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
            break;
            
        case LWS_CALLBACK_RECEIVE:
            if (len > 0) {
                char *message = malloc(len + 1);
                if (message) {
                    memcpy(message, in, len);
                    message[len] = '\0';
                    
                    log_info("Received WebSocket message");
                    
                    // Parse JSON message
                    cJSON* json = cJSON_Parse(message);
                    if (json && cJSON_IsArray(json)) {
                        // Get message type
                        cJSON* type = cJSON_GetArrayItem(json, 0);
                        if (type && cJSON_IsString(type)) {
                            const char* msg_type = cJSON_GetStringValue(type);
                            
                            if (strcmp(msg_type, "EVENT") == 0) {
                                // Handle EVENT message
                                cJSON* event = cJSON_GetArrayItem(json, 1);
                                if (event && cJSON_IsObject(event)) {
                                    char error_message[512] = {0};
                                    int result = handle_event_message(event, error_message, sizeof(error_message));
                                    
                                    // Broadcast event to matching persistent subscriptions
                                    if (result == 0) {
                                        broadcast_event_to_subscriptions(event);
                                    }
                                    
                                    // Send OK response
                                    cJSON* event_id = cJSON_GetObjectItem(event, "id");
                                    if (event_id && cJSON_IsString(event_id)) {
                                        cJSON* response = cJSON_CreateArray();
                                        cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
                                        cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
                                        cJSON_AddItemToArray(response, cJSON_CreateBool(result == 0));
                                        cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
                                        
                                        char *response_str = cJSON_Print(response);
                                        if (response_str) {
                                            size_t response_len = strlen(response_str);
                                            unsigned char *buf = malloc(LWS_PRE + response_len);
                                            if (buf) {
                                                memcpy(buf + LWS_PRE, response_str, response_len);
                                                lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
                                                free(buf);
                                            }
                                            free(response_str);
                                        }
                                        cJSON_Delete(response);
                                    }
                                }
                            } else if (strcmp(msg_type, "REQ") == 0) {
                                // Handle REQ message
                                cJSON* sub_id = cJSON_GetArrayItem(json, 1);
                                
                                if (sub_id && cJSON_IsString(sub_id)) {
                                    const char* subscription_id = cJSON_GetStringValue(sub_id);
                                    
                                    // Create array of filter objects from position 2 onwards
                                    cJSON* filters = cJSON_CreateArray();
                                    int json_size = cJSON_GetArraySize(json);
                                    for (int i = 2; i < json_size; i++) {
                                        cJSON* filter = cJSON_GetArrayItem(json, i);
                                        if (filter) {
                                            cJSON_AddItemToArray(filters, cJSON_Duplicate(filter, 1));
                                        }
                                    }
                                    
                                    handle_req_message(subscription_id, filters, wsi, pss);
                                    
                                    // Clean up the filters array we created
                                    cJSON_Delete(filters);
                                    
                                    // Send EOSE (End of Stored Events)
                                    cJSON* eose_response = cJSON_CreateArray();
                                    cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
                                    cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
                                    
                                    char *eose_str = cJSON_Print(eose_response);
                                    if (eose_str) {
                                        size_t eose_len = strlen(eose_str);
                                        unsigned char *buf = malloc(LWS_PRE + eose_len);
                                        if (buf) {
                                            memcpy(buf + LWS_PRE, eose_str, eose_len);
                                            lws_write(wsi, buf + LWS_PRE, eose_len, LWS_WRITE_TEXT);
                                            free(buf);
                                        }
                                        free(eose_str);
                                    }
                                    cJSON_Delete(eose_response);
                                }
                            } else if (strcmp(msg_type, "CLOSE") == 0) {
                                // Handle CLOSE message
                                cJSON* sub_id = cJSON_GetArrayItem(json, 1);
                                if (sub_id && cJSON_IsString(sub_id)) {
                                    const char* subscription_id = cJSON_GetStringValue(sub_id);
                                    
                                    // Remove from global manager
                                    remove_subscription_from_manager(subscription_id, wsi);
                                    
                                    // Remove from session list if present
                                    if (pss) {
                                        pthread_mutex_lock(&pss->session_lock);
                                        
                                        subscription_t** current = &pss->subscriptions;
                                        while (*current) {
                                            if (strcmp((*current)->id, subscription_id) == 0) {
                                                subscription_t* to_remove = *current;
                                                *current = to_remove->session_next;
                                                pss->subscription_count--;
                                                break;
                                            }
                                            current = &((*current)->session_next);
                                        }
                                        
                                        pthread_mutex_unlock(&pss->session_lock);
                                    }
                                    
                                    char debug_msg[256];
                                    snprintf(debug_msg, sizeof(debug_msg), "Closed subscription: %s", subscription_id);
                                    log_info(debug_msg);
                                }
                            }
                        }
                    }
                    
                    if (json) cJSON_Delete(json);
                    free(message);
                }
            }
            break;
            
        case LWS_CALLBACK_CLOSED:
            log_info("WebSocket connection closed");
            
            // Clean up session subscriptions
            if (pss) {
                pthread_mutex_lock(&pss->session_lock);
                
                subscription_t* sub = pss->subscriptions;
                while (sub) {
                    subscription_t* next = sub->session_next;
                    remove_subscription_from_manager(sub->id, wsi);
                    sub = next;
                }
                
                pss->subscriptions = NULL;
                pss->subscription_count = 0;
                
                pthread_mutex_unlock(&pss->session_lock);
                pthread_mutex_destroy(&pss->session_lock);
            }
            break;
            
        default:
            break;
    }
    
    return 0;
}

// WebSocket protocol definition
static struct lws_protocols protocols[] = {
    {
        "nostr-relay-protocol",
        nostr_relay_callback,
        sizeof(struct per_session_data),
        4096, // rx buffer size
        0, NULL, 0
    },
    { NULL, NULL, 0, 0, 0, NULL, 0 } // terminator
};

// Start libwebsockets-based WebSocket Nostr relay server
int start_websocket_relay() {
    struct lws_context_creation_info info;

    log_info("Starting libwebsockets-based Nostr relay server...");

    memset(&info, 0, sizeof(info));
    info.port = get_config_int("relay_port", DEFAULT_PORT);
    info.protocols = protocols;
    info.gid = -1;
    info.uid = -1;

    // Minimal libwebsockets configuration
    info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;

    // Remove interface restrictions - let system choose
    // info.vhost_name = NULL;
    // info.iface = NULL;

    // Increase max connections for relay usage
    info.max_http_header_pool = 16;
    info.timeout_secs = 10;

    // Max payload size for Nostr events
    info.max_http_header_data = 4096;

    ws_context = lws_create_context(&info);
    if (!ws_context) {
        log_error("Failed to create libwebsockets context");
        perror("libwebsockets creation error");
        return -1;
    }

    char startup_msg[256];
    snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", info.port);
    log_success(startup_msg);

    // Main event loop with proper signal handling
    while (g_server_running) {
        int result = lws_service(ws_context, 1000);

        if (result < 0) {
            log_error("libwebsockets service error");
            break;
        }
    }

    log_info("Shutting down WebSocket server...");
    lws_context_destroy(ws_context);
    ws_context = NULL;

    log_success("WebSocket relay shut down cleanly");
    return 0;
}




/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//             MAIN PROGRAM
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Print usage information
void print_usage(const char* program_name) {
    printf("Usage: %s [OPTIONS]\n", program_name);
    printf("\n");
    printf("C Nostr Relay Server\n");
    printf("\n");
    printf("Options:\n");
    printf("  -p, --port PORT    Listen port (default: %d)\n", DEFAULT_PORT);
    printf("  -h, --help         Show this help message\n");
    printf("\n");
}

int main(int argc, char* argv[]) {
    int port = DEFAULT_PORT;
    
    // Parse command line arguments
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_usage(argv[0]);
            return 0;
        } else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) {
            if (i + 1 < argc) {
                port = atoi(argv[++i]);
                if (port <= 0 || port > 65535) {
                    log_error("Invalid port number");
                    return 1;
                }
                // Store port in configuration system
                char port_str[16];
                snprintf(port_str, sizeof(port_str), "%d", port);
                set_database_config("relay_port", port_str, "command_line");
                // Re-apply configuration to make sure global variables are updated
                apply_configuration_to_globals();
            } else {
                log_error("Port argument requires a value");
                return 1;
            }
        } else {
            log_error("Unknown argument");
            print_usage(argv[0]);
            return 1;
        }
    }
    
    // Set up signal handlers
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    printf(BLUE BOLD "=== C Nostr Relay Server ===" RESET "\n");
    
    // Initialize database FIRST (required for configuration system)
    if (init_database() != 0) {
        log_error("Failed to initialize database");
        return 1;
    }
    
    // Initialize nostr library BEFORE configuration system
    // (required for Nostr event generation in config files)
    if (nostr_init() != 0) {
        log_error("Failed to initialize nostr library");
        close_database();
        return 1;
    }
    
    // Initialize configuration system (loads file + database + applies to globals)
    if (init_configuration_system() != 0) {
        log_error("Failed to initialize configuration system");
        nostr_cleanup();
        close_database();
        return 1;
    }
    
    // Initialize NIP-11 relay information
    init_relay_info();
    
    // Initialize NIP-13 PoW configuration
    init_pow_config();
    
    // Initialize NIP-40 expiration configuration
    init_expiration_config();
    
    // Update subscription manager configuration
    update_subscription_manager_config();
    
    log_info("Starting relay server...");
    
    // Start WebSocket Nostr relay server
    int result = start_websocket_relay();
    
    // Cleanup
    cleanup_relay_info();
    cleanup_configuration_system();
    nostr_cleanup();
    close_database();
    
    if (result == 0) {
        log_success("Server shutdown complete");
    } else {
        log_error("Server shutdown with errors");
    }
    
    return result;
}