/*
 * C-Relay Request Validator - Integrated Authentication System
 *
 * Provides complete request validation including:
 * - Protocol validation via nostr_core_lib (signatures, pubkey extraction,
 * NIP-42)
 * - Database-driven authorization rules (whitelist, blacklist, size limits)
 * - Memory caching for performance
 * - SQLite integration for C-relay needs
 */

#define _GNU_SOURCE
#include "../nostr_core_lib/cjson/cJSON.h"
#include "../nostr_core_lib/nostr_core/nip001.h"
#include "../nostr_core_lib/nostr_core/nip013.h"  // NIP-13: Proof of Work
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include "debug.h"  // C-relay debug system
#include "config.h"  // C-relay configuration system
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>

// External references to C-relay global state
extern sqlite3* g_db;
extern char g_database_path[512];

// Forward declaration for C-relay event storage function
extern int store_event(cJSON* event);

// External references to C-relay global PoW configuration (matching main.c)
extern 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
} g_pow_config;

// External references to C-relay expiration configuration (matching main.c)
extern 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
} g_expiration_config;

// Configuration functions from C-relay
extern int get_config_bool(const char* key, int default_value);
extern int get_config_int(const char* key, int default_value);

// NIP-42 constants (from nostr_core_lib)
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242

// NIP-42 error codes (from nostr_core_lib - already defined in nostr_common.h)

// Forward declarations for NIP-42 functions (simple implementations for C-relay)
int nostr_nip42_generate_challenge(char *challenge_buffer, size_t buffer_size);
int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
                                  const char *relay_url, int time_tolerance_seconds);

// Type definitions for unified request validation
typedef struct {
    const char *operation;        // HTTP method or operation type
    const char *auth_header;      // Authorization header value
    const char *client_ip;        // Client IP address
    const char *request_url;      // Full request URL
    const char *resource_hash;    // Resource hash for validation
    const char *challenge_id;     // NIP-42 challenge ID
    long file_size;               // File size for upload validation
    int nip42_enabled;            // Whether NIP-42 is enabled
} nostr_unified_request_t;

typedef struct {
    int valid;                    // Whether request is valid
    int error_code;               // Error code if invalid
    char reason[512];             // Reason for validation result
    char pubkey[65];              // Extracted pubkey from event
    unsigned char *file_data;     // File data for uploads
    size_t file_size;             // Size of file data
    int owns_file_data;           // Whether we own the file data memory
    char expected_hash[65];       // Expected file hash
} nostr_request_result_t;

// Additional error codes for ginxsom-specific functionality
#define NOSTR_ERROR_CRYPTO_INIT -100
#define NOSTR_ERROR_AUTH_REQUIRED -101
#define NOSTR_ERROR_NIP42_DISABLED -102
#define NOSTR_ERROR_EVENT_EXPIRED -103
// Note: NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND and
// NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED are already defined in
// nostr_core_lib/nostr_core/nostr_common.h

// Note: Using g_database_path from C-relay global state instead of hardcoded path

// NIP-42 challenge management constants
#define MAX_CHALLENGES 1000
#define CHALLENGE_CLEANUP_INTERVAL 300 // 5 minutes

//=============================================================================
// DATA STRUCTURES
//=============================================================================

// NIP-42 challenge storage
typedef struct {
  char challenge_id[65];
  char client_ip[64];
  time_t created_at;
  time_t expires_at;
  int active;
} nip42_challenge_entry_t;

// NIP-42 challenge management
typedef struct {
  nip42_challenge_entry_t challenges[MAX_CHALLENGES];
  int challenge_count;
  time_t last_cleanup;
  int timeout_seconds;
  int time_tolerance_seconds;
} nip42_challenge_manager_t;

//=============================================================================
// GLOBAL STATE
//=============================================================================

// No longer using local auth cache - using unified cache from config.c
static nip42_challenge_manager_t g_challenge_manager = {0};
static int g_validator_initialized = 0;

// Last rule violation details for status code mapping
struct {
  char violation_type[100]; // "pubkey_blacklist", "hash_blacklist",
                            // "whitelist_violation", etc.
  char reason[500];         // specific reason string
} g_last_rule_violation = {0};


//=============================================================================
// FORWARD DECLARATIONS
//=============================================================================

static int reload_auth_config(void);
// Removed unused forward declarations for functions that are no longer called
int check_database_auth_rules(const char *pubkey, const char *operation,
                              const char *resource_hash);
void nostr_request_validator_clear_violation(void);

// NIP-42 challenge management functions
static void cleanup_expired_challenges(void);
static int store_challenge(const char *challenge_id, const char *client_ip);
static int generate_challenge_id(char *challenge_buffer, size_t buffer_size);

//=============================================================================
// MAIN API FUNCTIONS
//=============================================================================

/**
 * Initialize the ginxsom request validator system
 */
int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
  // Mark db_path as unused to suppress warning - it's for future use
  (void)db_path;
  (void)app_name;

  if (g_validator_initialized) {
    return NOSTR_SUCCESS; // Already initialized
  }

  // Initialize nostr_core_lib if not already done
  if (nostr_crypto_init() != NOSTR_SUCCESS) {
    return NOSTR_ERROR_CRYPTO_INIT;
  }

  // Load initial configuration from database
  int result = reload_auth_config();
  if (result != NOSTR_SUCCESS) {
    return result;
  }

  // Initialize NIP-42 challenge manager using unified config
  memset(&g_challenge_manager, 0, sizeof(g_challenge_manager));

  const char* nip42_timeout = get_config_value("nip42_challenge_timeout");
  g_challenge_manager.timeout_seconds = nip42_timeout ? atoi(nip42_timeout) : 600;
  if (nip42_timeout) free((char*)nip42_timeout);

  const char* nip42_tolerance = get_config_value("nip42_time_tolerance");
  g_challenge_manager.time_tolerance_seconds = nip42_tolerance ? atoi(nip42_tolerance) : 300;
  if (nip42_tolerance) free((char*)nip42_tolerance);

  g_challenge_manager.last_cleanup = time(NULL);

  g_validator_initialized = 1;
  return NOSTR_SUCCESS;
}

/**
 * Check if authentication rules are enabled
 */
int nostr_auth_rules_enabled(void) {
  // Use unified cache from config.c
  const char* auth_enabled = get_config_value("auth_enabled");
  int result = 0;
  if (auth_enabled && strcmp(auth_enabled, "true") == 0) {
    result = 1;
  }
  if (auth_enabled) free((char*)auth_enabled);

  // Also check legacy key
  const char* auth_rules_enabled = get_config_value("auth_rules_enabled");
  if (auth_rules_enabled && strcmp(auth_rules_enabled, "true") == 0) {
    result = 1;
  }
  if (auth_rules_enabled) free((char*)auth_rules_enabled);

  return result;
}

///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
//  MAIN VALIDATION OF REQUEST
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
int nostr_validate_unified_request(const char* json_string, size_t json_length) {
  // Clear previous violation details
  nostr_request_validator_clear_violation();

  /////////////////////////////////////////////////////////////////////
  //  PHASE 1: INPUT VALIDATION (Immediate Rejection ~1μs)
  /////////////////////////////////////////////////////////////////////

  // 1. Null Pointer Checks - Reject malformed requests instantly
  if (!json_string || json_length == 0) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // 2. Initialization Check - Verify system is properly initialized
  if (!g_validator_initialized) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // 3. Parse JSON string to cJSON event object
  cJSON *event = cJSON_ParseWithLength(json_string, json_length);
  if (!event) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // 4. Validate basic event structure
  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 *tags = cJSON_GetObjectItem(event, "tags");
  cJSON *content = cJSON_GetObjectItem(event, "content");
  cJSON *sig = cJSON_GetObjectItem(event, "sig");

  if (!id || !cJSON_IsString(id) ||
      !pubkey || !cJSON_IsString(pubkey) ||
      !created_at || !cJSON_IsNumber(created_at) ||
      !kind || !cJSON_IsNumber(kind) ||
      !tags || !cJSON_IsArray(tags) ||
      !content || !cJSON_IsString(content) ||
      !sig || !cJSON_IsString(sig)) {
    cJSON_Delete(event);
    return NOSTR_ERROR_INVALID_INPUT;
  }

  int event_kind = (int)cJSON_GetNumberValue(kind);

  // 5. Check configuration using unified cache
  int auth_required = nostr_auth_rules_enabled();

  /////////////////////////////////////////////////////////////////////
  //  PHASE 2: NOSTR EVENT VALIDATION
  /////////////////////////////////////////////////////////////////////

  // 6. Nostr Event Structure Validation using nostr_core_lib
  int validation_result = nostr_validate_event(event);
  if (validation_result != NOSTR_SUCCESS) {
    cJSON_Delete(event);
    return validation_result;
  }

  // 7. Extract pubkey for rule evaluation
  const char *event_pubkey = cJSON_GetStringValue(pubkey);
  if (!event_pubkey || strlen(event_pubkey) != 64) {
    cJSON_Delete(event);
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }

  /////////////////////////////////////////////////////////////////////
  //  PHASE 3: ADMIN EVENT BYPASS CHECK
  /////////////////////////////////////////////////////////////////////

  // 8. Check if this is a kind 23456 admin event from authorized admin
  // This must happen AFTER signature validation but BEFORE auth rules
  if (event_kind == 23456) {
    const char* admin_pubkey = get_config_value("admin_pubkey");
    if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) {
      // Valid admin event - bypass remaining validation
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }
    // Not from admin - continue with normal validation
  }

  /////////////////////////////////////////////////////////////////////
  //  PHASE 4: EVENT KIND SPECIFIC VALIDATION
  /////////////////////////////////////////////////////////////////////

  // 9. Handle NIP-42 authentication challenge events (kind 22242)
  if (event_kind == 22242) {
    // Check NIP-42 mode using unified cache
    const char* nip42_enabled = get_config_value("nip42_auth_enabled");
    if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) {
      free((char*)nip42_enabled);
      cJSON_Delete(event);
      return NOSTR_ERROR_NIP42_DISABLED;
    }
    if (nip42_enabled) free((char*)nip42_enabled);

    // TODO: Implement full NIP-42 challenge validation
    // For now, accept all valid NIP-42 events
    cJSON_Delete(event);
    return NOSTR_SUCCESS;
  }

  /////////////////////////////////////////////////////////////////////
  //  PHASE 5: AUTHENTICATION RULES (Database Queries)
  /////////////////////////////////////////////////////////////////////

  // 10. Check if authentication rules are enabled
  if (!auth_required) {
  } else {
    // 11. Check database authentication rules (only if auth enabled)

    // Create operation string with event kind for more specific rule matching
    char operation_str[64];
    snprintf(operation_str, sizeof(operation_str), "event_kind_%d", event_kind);

    // Also check generic "event" operation for backward compatibility
    int rules_result = check_database_auth_rules(event_pubkey, "event", NULL);
    if (rules_result != NOSTR_SUCCESS) {
      // If generic event check fails, try specific event kind check
      rules_result = check_database_auth_rules(event_pubkey, operation_str, NULL);
      if (rules_result != NOSTR_SUCCESS) {
        cJSON_Delete(event);
        return rules_result;
      }
    }
  }

  /////////////////////////////////////////////////////////////////////
  //  PHASE 6: ADDITIONAL VALIDATIONS (C-relay specific)
  /////////////////////////////////////////////////////////////////////

  // 12. NIP-13 Proof of Work validation
  int pow_enabled = get_config_bool("pow_enabled", 0);
  int pow_min_difficulty = get_config_int("pow_min_difficulty", 0);
  int pow_validation_flags = get_config_int("pow_validation_flags", 1);

  if (pow_enabled && pow_min_difficulty > 0) {
    nostr_pow_result_t pow_result;
    int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
                                                   pow_validation_flags, &pow_result);

    if (pow_validation_result != NOSTR_SUCCESS) {
      cJSON_Delete(event);
      return pow_validation_result;
    }
  }

  // 13. NIP-40 Expiration validation
  // Always check expiration tags if present (following NIP-40 specification)

  cJSON *expiration_tag = NULL;
  cJSON *tags_array = cJSON_GetObjectItem(event, "tags");

  if (tags_array && cJSON_IsArray(tags_array)) {
    cJSON *tag = NULL;
    cJSON_ArrayForEach(tag, tags_array) {
      if (!cJSON_IsArray(tag)) continue;

      cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
      if (!tag_name || !cJSON_IsString(tag_name)) continue;

      const char *tag_name_str = cJSON_GetStringValue(tag_name);
      if (strcmp(tag_name_str, "expiration") == 0) {
        cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
        if (tag_value && cJSON_IsString(tag_value)) {
          expiration_tag = tag_value;
          break;
        }
      }
    }
  }

  if (expiration_tag) {
    const char *expiration_str = cJSON_GetStringValue(expiration_tag);

    // Validate that the expiration string contains only digits (and optional leading whitespace)
    const char* p = expiration_str;

    // Skip leading whitespace
    while (*p == ' ' || *p == '\t') p++;

    // Check if we have at least one digit
    if (*p == '\0') {
    } else {
      // Validate that all remaining characters are digits
      const char* digit_start = p;
      while (*p >= '0' && *p <= '9') p++;

      // If we didn't consume the entire string or found no digits, it's malformed
      if (*p != '\0' || p == digit_start) {
      } else {
        // Valid numeric string, parse and check expiration
        time_t expiration_time = (time_t)atol(expiration_str);
        time_t now = time(NULL);
        int grace_period = get_config_int("nip40_expiration_grace_period", 60);

        if (expiration_time > 0 && now > expiration_time + grace_period) {
          cJSON_Delete(event);
          return NOSTR_ERROR_EVENT_EXPIRED;
        }
      }
    }
  }

  // All validations passed
  cJSON_Delete(event);
  return NOSTR_SUCCESS;
}

/**
 * Generate NIP-42 challenge for clients
 */
int nostr_request_validator_generate_nip42_challenge(void *challenge_struct,
                                                     const char *client_ip) {
  // Mark client_ip as unused to suppress warning - it's for future enhancement
  (void)client_ip;

  // Use nostr_core_lib NIP-42 functionality
  char challenge_id[65];
  int result = nostr_nip42_generate_challenge(challenge_id, 32);
  if (result != NOSTR_SUCCESS) {
    return result;
  }

  // Fill challenge structure (assuming it's a compatible structure)
  // This is a simplified implementation - adjust based on actual structure
  // needs
  if (challenge_struct) {
    // Cast to appropriate structure and fill fields
    // For now, just return success
  }

  return NOSTR_SUCCESS;
}

/**
 * Get the last rule violation type for status code mapping
 */
const char *nostr_request_validator_get_last_violation_type(void) {
  return g_last_rule_violation.violation_type;
}

/**
 * Clear the last rule violation details
 */
void nostr_request_validator_clear_violation(void) {
  memset(&g_last_rule_violation, 0, sizeof(g_last_rule_violation));
}

/**
 * Cleanup request validator resources
 */
void ginxsom_request_validator_cleanup(void) {
  g_validator_initialized = 0;
  nostr_request_validator_clear_violation();
}

/**
 * Free file data allocated by validator
 */
void nostr_request_result_free_file_data(nostr_request_result_t *result) {
  if (result && result->file_data && result->owns_file_data) {
    free(result->file_data);
    result->file_data = NULL;
    result->file_size = 0;
    result->owns_file_data = 0;
  }
}

//=============================================================================
// HELPER FUNCTIONS
//=============================================================================


/**
 * Force cache refresh - cache no longer exists, function kept for compatibility
 */
void nostr_request_validator_force_cache_refresh(void) {
  // Cache no longer exists - direct database queries are used
}

/**
 * This function is no longer needed - configuration is handled by unified cache
 */
static int reload_auth_config(void) {
  // Configuration is now handled by the unified cache in config.c
  return NOSTR_SUCCESS;
}

// Removed unused functions: parse_authorization_header, extract_pubkey_from_event

// Note: Blossom protocol validation removed - C-relay uses standard Nostr events only

/**
 * Check database authentication rules for the request
 * Implements the 6-step rule evaluation engine from AUTH_API.md
 */
int check_database_auth_rules(const char *pubkey, const char *operation __attribute__((unused)),
                              const char *resource_hash) {
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;
  int rc;

  DEBUG_TRACE("Checking auth rules for pubkey: %s", pubkey);

  if (!pubkey) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Open database using global database path
  if (strlen(g_database_path) == 0) {
    return NOSTR_SUCCESS; // Default allow on DB error
  }

  rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
  if (rc != SQLITE_OK) {
    return NOSTR_SUCCESS; // Default allow on DB error
  }

  // Step 1: Check pubkey blacklist (highest priority)
  const char *blacklist_sql =
      "SELECT rule_type FROM auth_rules WHERE rule_type = "
      "'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
  DEBUG_TRACE("Blacklist SQL: %s", blacklist_sql);
  rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);

    int step_result = sqlite3_step(stmt);
    DEBUG_TRACE("Blacklist query result: %s", step_result == SQLITE_ROW ? "FOUND" : "NOT_FOUND");

    if (step_result == SQLITE_ROW) {
      DEBUG_TRACE("BLACKLIST HIT: Denying access for pubkey: %s", pubkey);
      // Set specific violation details for status code mapping
      strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
      sprintf(g_last_rule_violation.reason, "Public key blacklisted");

      sqlite3_finalize(stmt);
      sqlite3_close(db);
      return NOSTR_ERROR_AUTH_REQUIRED;
    }
    sqlite3_finalize(stmt);
  }

  // Step 2: Check hash blacklist
  if (resource_hash) {
    const char *hash_blacklist_sql =
        "SELECT rule_type FROM auth_rules WHERE rule_type = "
        "'blacklist' AND pattern_type = 'hash' AND pattern_value = ? AND active = 1 LIMIT 1";
    rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
      sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);

      if (sqlite3_step(stmt) == SQLITE_ROW) {
        // Set specific violation details for status code mapping
        strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
        sprintf(g_last_rule_violation.reason, "File hash blacklisted");

        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return NOSTR_ERROR_AUTH_REQUIRED;
      }
      sqlite3_finalize(stmt);
    }
  }

  // Step 3: Check pubkey whitelist
  const char *whitelist_sql =
      "SELECT rule_type FROM auth_rules WHERE rule_type = "
      "'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
  rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) == SQLITE_ROW) {
      sqlite3_finalize(stmt);
      sqlite3_close(db);
      return NOSTR_SUCCESS; // Allow whitelisted pubkey
    }
    sqlite3_finalize(stmt);
  }

  // Step 4: Check if any whitelist rules exist - if yes, deny by default
  const char *whitelist_exists_sql =
      "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'whitelist' "
      "AND pattern_type = 'pubkey' AND active = 1 LIMIT 1";
  rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    if (sqlite3_step(stmt) == SQLITE_ROW) {
      int whitelist_count = sqlite3_column_int(stmt, 0);
      if (whitelist_count > 0) {
        // Set specific violation details for status code mapping
        strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
        strcpy(g_last_rule_violation.reason,
               "Public key not whitelisted for this operation");

        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return NOSTR_ERROR_AUTH_REQUIRED;
      }
    }
    sqlite3_finalize(stmt);
  }

  sqlite3_close(db);
  return NOSTR_SUCCESS; // Default allow if no restrictive rules matched
}

// Removed unused functions: validate_nip42_event, validate_admin_event

//=============================================================================
// NIP-42 CHALLENGE MANAGEMENT FUNCTIONS
//=============================================================================

/**
 * Generate a challenge ID using nostr_core_lib
 */
static int generate_challenge_id(char *challenge_buffer, size_t buffer_size) {
  if (!challenge_buffer || buffer_size < 65) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Use nostr_core_lib to generate a random challenge
  return nostr_nip42_generate_challenge(challenge_buffer, 32);
}

/**
 * Clean up expired challenges from memory
 */
static void cleanup_expired_challenges(void) {
  time_t now = time(NULL);

  // Only cleanup if enough time has passed
  if (now - g_challenge_manager.last_cleanup < CHALLENGE_CLEANUP_INTERVAL) {
    return;
  }

  int active_count = 0;
  for (int i = 0; i < g_challenge_manager.challenge_count; i++) {
    if (g_challenge_manager.challenges[i].active) {
      if (now > g_challenge_manager.challenges[i].expires_at) {
        // Mark expired challenge as inactive
        g_challenge_manager.challenges[i].active = 0;
        memset(g_challenge_manager.challenges[i].challenge_id, 0,
               sizeof(g_challenge_manager.challenges[i].challenge_id));
      } else {
        active_count++;
      }
    }
  }

  // Compact the array if we have many inactive entries
  if (active_count < g_challenge_manager.challenge_count / 2 &&
      active_count < MAX_CHALLENGES - 100) {
    int write_idx = 0;
    for (int read_idx = 0; read_idx < g_challenge_manager.challenge_count;
         read_idx++) {
      if (g_challenge_manager.challenges[read_idx].active) {
        if (write_idx != read_idx) {
          memcpy(&g_challenge_manager.challenges[write_idx],
                 &g_challenge_manager.challenges[read_idx],
                 sizeof(nip42_challenge_entry_t));
        }
        write_idx++;
      }
    }
    g_challenge_manager.challenge_count = write_idx;
  }

  g_challenge_manager.last_cleanup = now;
}

/**
 * Store a new challenge in memory
 */
static int store_challenge(const char *challenge_id, const char *client_ip) {
  if (!challenge_id || strlen(challenge_id) == 0) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  cleanup_expired_challenges();

  // Find an available slot
  int slot_idx = -1;

  // First, try to find an inactive slot
  for (int i = 0; i < g_challenge_manager.challenge_count; i++) {
    if (!g_challenge_manager.challenges[i].active) {
      slot_idx = i;
      break;
    }
  }

  // If no inactive slot found, use next available if we haven't hit max
  if (slot_idx == -1 && g_challenge_manager.challenge_count < MAX_CHALLENGES) {
    slot_idx = g_challenge_manager.challenge_count++;
  }

  // If still no slot, we're full - remove oldest entry
  if (slot_idx == -1) {
    slot_idx = 0; // Overwrite first entry (oldest)
  }

  // Store the new challenge
  nip42_challenge_entry_t *entry = &g_challenge_manager.challenges[slot_idx];
  memset(entry, 0, sizeof(nip42_challenge_entry_t));

  // Store challenge with proper length handling (up to buffer size - 1)
  strncpy(entry->challenge_id, challenge_id, sizeof(entry->challenge_id) - 1);
  entry->challenge_id[sizeof(entry->challenge_id) - 1] = '\0';

  if (client_ip) {
    strncpy(entry->client_ip, client_ip, sizeof(entry->client_ip) - 1);
    entry->client_ip[sizeof(entry->client_ip) - 1] = '\0';
  }

  time_t now = time(NULL);
  entry->created_at = now;
  entry->expires_at = now + g_challenge_manager.timeout_seconds;
  entry->active = 1;

  return NOSTR_SUCCESS;
}


/**
 * Generate and store a new NIP-42 challenge for /auth endpoint
 */
int nostr_generate_nip42_challenge(char *challenge_out, size_t challenge_size,
                                   const char *client_ip) {
  if (!challenge_out || challenge_size < 65) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Generate challenge ID
  int result = generate_challenge_id(challenge_out, challenge_size);
  if (result != NOSTR_SUCCESS) {
    return result;
  }

  // Store in challenge manager
  result = store_challenge(challenge_out, client_ip);
  if (result != NOSTR_SUCCESS) {
    return result;
  }

  return NOSTR_SUCCESS;
}

//=============================================================================
// SIMPLE NIP-42 FUNCTION IMPLEMENTATIONS FOR C-RELAY
//=============================================================================

/**
 * Simple NIP-42 challenge generation (generates random hex string)
 */
int nostr_nip42_generate_challenge(char *challenge_buffer, size_t buffer_size) {
  if (!challenge_buffer || buffer_size < 65) {
    return NOSTR_ERROR_INVALID_INPUT;
  }
  
  // Generate 32 random bytes and convert to hex string
  unsigned char random_bytes[32];
  
  // Simple random number generation using time and rand()
  srand((unsigned int)time(NULL));
  for (int i = 0; i < 32; i++) {
    random_bytes[i] = (unsigned char)(rand() % 256);
  }
  
  // Convert to hex string
  for (int i = 0; i < 32; i++) {
    sprintf(&challenge_buffer[i * 2], "%02x", random_bytes[i]);
  }
  challenge_buffer[64] = '\0';
  
  return NOSTR_SUCCESS;
}

/**
 * Simple NIP-42 auth event verification (validates basic structure and expiration)
 */
int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
                                  const char *relay_url, int time_tolerance_seconds) {
  if (!event || !challenge_id || !relay_url) {
    return NOSTR_ERROR_INVALID_INPUT;
  }
  
  // Check if event has the required tags for NIP-42
  cJSON *tags = cJSON_GetObjectItem(event, "tags");
  if (!tags || !cJSON_IsArray(tags)) {
    return NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID;
  }
  
  int has_relay_tag = 0;
  int has_challenge_tag = 0;
  time_t expiration = 0;
  
  // Look for required tags
  cJSON *tag = NULL;
  cJSON_ArrayForEach(tag, tags) {
    if (!cJSON_IsArray(tag)) continue;
    
    cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
    if (!tag_name || !cJSON_IsString(tag_name)) continue;
    
    const char *tag_name_str = cJSON_GetStringValue(tag_name);
    
    if (strcmp(tag_name_str, "relay") == 0) {
      has_relay_tag = 1;
      cJSON *relay_value = cJSON_GetArrayItem(tag, 1);
      if (relay_value && cJSON_IsString(relay_value)) {
        const char *event_relay = cJSON_GetStringValue(relay_value);
        // For C-relay, accept the relay URL as valid (basic check)
        if (event_relay && strlen(event_relay) > 0) {
          // Relay URL validation passed
        }
      }
    } else if (strcmp(tag_name_str, "challenge") == 0) {
      has_challenge_tag = 1;
      cJSON *challenge_value = cJSON_GetArrayItem(tag, 1);
      if (challenge_value && cJSON_IsString(challenge_value)) {
        const char *event_challenge = cJSON_GetStringValue(challenge_value);
        if (!event_challenge || strcmp(event_challenge, challenge_id) != 0) {
          return NOSTR_ERROR_NIP42_INVALID_CHALLENGE;
        }
      }
    } else if (strcmp(tag_name_str, "expiration") == 0) {
      cJSON *exp_value = cJSON_GetArrayItem(tag, 1);
      if (exp_value && cJSON_IsString(exp_value)) {
        const char *exp_str = cJSON_GetStringValue(exp_value);
        
        // Validate that the expiration string contains only digits (and optional leading whitespace)
        const char* p = exp_str;
        
        // Skip leading whitespace
        while (*p == ' ' || *p == '\t') p++;
        
        // Check if we have at least one digit and all remaining characters are digits
        if (*p != '\0') {
          const char* digit_start = p;
          while (*p >= '0' && *p <= '9') p++;
          
          // If we consumed the entire string and found at least one digit, it's valid
          if (*p == '\0' && p > digit_start) {
            expiration = (time_t)atol(exp_str);
          }
          // If malformed, expiration remains 0 (no expiration), effectively ignoring the tag
        }
      }
    }
  }
  
  // Check required tags are present
  if (!has_relay_tag || !has_challenge_tag) {
    return NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID;
  }
  
  // Check expiration with time tolerance
  time_t now = time(NULL);
  if (expiration > 0) {
    if (now > expiration + time_tolerance_seconds) {
      return NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED;
    }
  }
  
  // Check created_at timestamp for reasonable bounds
  cJSON *created_at_json = cJSON_GetObjectItem(event, "created_at");
  if (created_at_json && cJSON_IsNumber(created_at_json)) {
    time_t created_at = (time_t)cJSON_GetNumberValue(created_at_json);
    if (abs((int)(now - created_at)) > time_tolerance_seconds) {
      return NOSTR_ERROR_NIP42_TIME_TOLERANCE;
    }
  }
  
  return NOSTR_SUCCESS;
}
