/*
 * Ginxsom 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 ginxsom-specific 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/nip042.h"
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include "ginxsom.h"
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>

#define MAX_MIME_TYPE_LEN 128 // Define here for direct use

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

// Use global database path from main.c
extern char g_db_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;

// Cached configuration structure
typedef struct {
  int auth_required;           // Whether authentication is required
  long max_file_size;          // Maximum file size in bytes
  int admin_enabled;           // Whether admin interface is enabled
  char admin_pubkey[65];       // Admin public key
  int nip42_mode;              // NIP-42 authentication mode
  int nip42_challenge_timeout; // NIP-42 challenge timeout in seconds
  int nip42_time_tolerance;    // NIP-42 time tolerance in seconds
  time_t cache_expires;        // When cache expires
  int cache_valid;             // Whether cache is valid
} auth_config_cache_t;

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

static auth_config_cache_t g_auth_cache = {0};
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};

/**
 * Helper function for consistent validator debug logging via app logger
 */
static void validator_debug_log(const char *message) {
  const char *msg = message ? message : "";
  size_t msg_len = strlen(msg);

  if (msg_len > 0 && msg[msg_len - 1] == '\n') {
    char trimmed[1024];
    size_t copy_len = msg_len - 1;
    if (copy_len >= sizeof(trimmed)) {
      copy_len = sizeof(trimmed) - 1;
    }
    memcpy(trimmed, msg, copy_len);
    trimmed[copy_len] = '\0';
    app_log(LOG_DEBUG, "%s", trimmed);
    return;
  }

  app_log(LOG_DEBUG, "%s", msg);
}

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

static int reload_auth_config(void);
static int parse_authorization_header(const char *auth_header, char *event_json,
                                      size_t json_size);
static int extract_pubkey_from_event(cJSON *event, char *pubkey_buffer,
                                     size_t buffer_size);
static int validate_blossom_event(cJSON *event, const char *expected_hash,
                                  const char *method);
static int validate_nip42_event(cJSON *event, const char *relay_url,
                                const char *challenge_id);
static int validate_admin_event(cJSON *event, const char *method, const char *endpoint);
static int check_database_auth_rules(const char *pubkey, const char *operation,
                                      const char *resource_hash, const char *mime_type);
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 validate_challenge(const char *challenge_id);
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_init() != NOSTR_SUCCESS) {
    validator_debug_log(
        "VALIDATOR: Failed to initialize nostr core system\n");
    return NOSTR_ERROR_CRYPTO_INIT;
  }

  // Load initial configuration from database
  int result = reload_auth_config();
  if (result != NOSTR_SUCCESS) {
    validator_debug_log(
        "VALIDATOR: Failed to load configuration from database\n");
    return result;
  }

  // Initialize NIP-42 challenge manager
  memset(&g_challenge_manager, 0, sizeof(g_challenge_manager));
  g_challenge_manager.timeout_seconds =
      g_auth_cache.nip42_challenge_timeout > 0
          ? g_auth_cache.nip42_challenge_timeout
          : 600;
  g_challenge_manager.time_tolerance_seconds =
      g_auth_cache.nip42_time_tolerance > 0 ? g_auth_cache.nip42_time_tolerance
                                            : 300;
  g_challenge_manager.last_cleanup = time(NULL);

  g_validator_initialized = 1;
  validator_debug_log(
      "VALIDATOR: Request validator initialized successfully\n");
  return NOSTR_SUCCESS;
}

/**
 * Check if authentication rules are enabled
 */
int nostr_auth_rules_enabled(void) {
  // Reload config if cache expired
  if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
    reload_auth_config();
  }

  return g_auth_cache.auth_required;
}

///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
//  MAIN VALIDATION OF REQUEST
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
int nostr_validate_unified_request(const nostr_unified_request_t *request,
                                   nostr_request_result_t *result) {
  // 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 (!request || !result) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

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

  // 3. Basic Structure Validation - Ensure required fields are present
  // Initialize result structure
  memset(result, 0, sizeof(nostr_request_result_t));
  result->valid = 1; // Default allow
  result->error_code = NOSTR_SUCCESS;
  strcpy(result->reason, "No validation required");
  result->file_data = NULL;
  result->file_size = 0;
  result->owns_file_data = 0;
  memset(result->expected_hash, 0, sizeof(result->expected_hash));

  // Reload config if needed
  if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) {
    reload_auth_config();
  }
  char config_msg[256];
  sprintf(config_msg,
          "VALIDATOR_DEBUG: STEP 3 PASSED - Configuration loaded "
          "(auth_required=%d, nip42_enabled=%d)\n",
          g_auth_cache.auth_required, request->nip42_enabled);

  // Handle challenge generation operation (no authentication required)
  if (request->operation && strcmp(request->operation, "challenge") == 0) {

    // Check if NIP-42 is enabled
    if (!request->nip42_enabled || g_auth_cache.nip42_mode == 0) {

      result->valid = 0;
      result->error_code = NOSTR_ERROR_NIP42_DISABLED;
      strcpy(result->reason, "NIP-42 authentication is disabled");
      return NOSTR_SUCCESS;
    }

    // Generate and store challenge
    char challenge_id[65];
    int challenge_result =
        generate_challenge_id(challenge_id, sizeof(challenge_id));
    if (challenge_result != NOSTR_SUCCESS) {

      result->valid = 0;
      result->error_code = challenge_result;
      strcpy(result->reason, "Failed to generate challenge ID");
      return NOSTR_SUCCESS;
    }

    // Store challenge in manager
    int store_result = store_challenge(challenge_id, request->client_ip);
    if (store_result != NOSTR_SUCCESS) {

      result->valid = 0;
      result->error_code = store_result;
      strcpy(result->reason, "Failed to store challenge");
      return NOSTR_SUCCESS;
    }

    // Return challenge in result (we'll use the reason field for the challenge
    // ID)
    snprintf(result->reason, sizeof(result->reason), "CHALLENGE:%s",
             challenge_id);
    result->valid = 1;
    result->error_code = NOSTR_SUCCESS;

    char challenge_msg[256];
    sprintf(challenge_msg,
            "VALIDATOR_DEBUG: STEP 4 PASSED - Challenge generated: %.16s...\n",
            challenge_id);

    return NOSTR_SUCCESS;
  }

  /////////////////////////////////////////////////////////////////////
  //  PHASE 2: NOSTR EVENT VALIDATION (CPU Intensive ~2ms)
  /////////////////////////////////////////////////////////////////////

  // Check if authentication is disabled first (regardless of header presence)
  if (!g_auth_cache.auth_required) {
    validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication "
                        "disabled, allowing request\n");
    result->valid = 1;
    result->error_code = NOSTR_SUCCESS;
    strcpy(result->reason, "Authentication disabled");

    // Even when auth is disabled, try to extract uploader pubkey from a provided
    // auth header so uploads can still be attributed in blob metadata.
    if (request->auth_header) {
      char optional_event_json[4096];
      int optional_parse = parse_authorization_header(
          request->auth_header, optional_event_json, sizeof(optional_event_json));
      if (optional_parse == NOSTR_SUCCESS) {
        cJSON *optional_event = cJSON_Parse(optional_event_json);
        if (optional_event) {
          if (nostr_validate_event(optional_event) == NOSTR_SUCCESS) {
            char optional_pubkey[65] = {0};
            int optional_extract = extract_pubkey_from_event(
                optional_event, optional_pubkey, sizeof(optional_pubkey));
            if (optional_extract == NOSTR_SUCCESS && strlen(optional_pubkey) == 64) {
              strncpy(result->pubkey, optional_pubkey, 64);
              result->pubkey[64] = '\0';
              strcpy(result->reason,
                     "Authentication disabled (pubkey extracted from auth header)");
            }
          }
          cJSON_Delete(optional_event);
        }
      }
    }

    return NOSTR_SUCCESS;
  }

  // Check if this is a BUD-09 report request - allow anonymous reporting
  if (request->operation && strcmp(request->operation, "report") == 0) {
    // BUD-09 allows anonymous reporting - pass through to bud09.c for validation
    result->valid = 1;
    result->error_code = NOSTR_SUCCESS;
    strcpy(result->reason, "BUD-09 report request - bypassing auth for anonymous reporting");
    validator_debug_log("VALIDATOR_DEBUG: BUD-09 report detected, bypassing authentication\n");
    return NOSTR_SUCCESS;
  }

  // Check if authentication header is provided (required for non-report operations)
  if (!request->auth_header) {

    result->valid = 0;
    result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
    strcpy(result->reason, "Authentication required but not provided");
    return NOSTR_SUCCESS;
  }
  char header_msg[110];
  sprintf(header_msg,
          "VALIDATOR_DEBUG: STEP 4 PASSED - Auth header provided: %.50s...\n",
          request->auth_header);

  // 4. Authorization Header Parsing - Extract base64-encoded Nostr event
  //    Format: "Authorization: Nostr <base64>"
  //    Early exit: Invalid base64 or malformed header rejected immediately
  char event_json[4096];
  int parse_result = parse_authorization_header(request->auth_header,
                                                event_json, sizeof(event_json));
  if (parse_result != NOSTR_SUCCESS) {
    char parse_msg[256];
    sprintf(parse_msg,
            "VALIDATOR_DEBUG: STEP 5 FAILED - Failed to parse authorization "
            "header (error=%d)\n",
            parse_result);

    result->valid = 0;
    result->error_code = parse_result;
    strcpy(result->reason, "Invalid authorization header format. Must be 'Nostr <base64-encoded-event>'");
    return NOSTR_SUCCESS;
  }
  char parse_success_msg[512];
  sprintf(parse_success_msg,
          "VALIDATOR_DEBUG: STEP 5 PASSED - Authorization header parsed, JSON: "
          "%.100s...\n",
          event_json);

  // 5. JSON Parsing - Parse Nostr event JSON using cJSON
  //    Early exit: Invalid JSON rejected before signature verification
  cJSON *event = cJSON_Parse(event_json);
  if (!event) {

    result->valid = 0;
    result->error_code = NOSTR_ERROR_EVENT_INVALID_CONTENT;
    strcpy(result->reason, "Invalid JSON in authorization header. Ensure event is properly formatted JSON.");
    return NOSTR_SUCCESS;
  }

  // 6. Nostr Event Structure Validation - Validate required fields
  //    Early exit: Invalid structure rejected before expensive crypto
  //    operations
  int validation_result = nostr_validate_event(event);
  if (validation_result != NOSTR_SUCCESS) {
    char validation_msg[256];
    sprintf(validation_msg,
            "VALIDATOR_DEBUG: STEP 7 FAILED - NOSTR event validation failed "
            "(error=%d)\n",
            validation_result);

    result->valid = 0;
    result->error_code = validation_result;
    
    // Map event validation errors to detailed messages
    switch (validation_result) {
      case NOSTR_ERROR_EVENT_INVALID_STRUCTURE:
        strcpy(result->reason, "Event structure invalid. Missing required fields: id, pubkey, created_at, kind, tags, content, sig");
        break;
      case NOSTR_ERROR_EVENT_INVALID_ID:
        strcpy(result->reason, "Event ID verification failed. Check event serialization and hash calculation.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_PUBKEY:
        strcpy(result->reason, "Invalid pubkey format. Must be 64-character hex string.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
        strcpy(result->reason, "Event signature verification failed. Check private key and signing process.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_CREATED_AT:
        strcpy(result->reason, "Invalid created_at timestamp. Must be valid Unix timestamp.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_KIND:
        strcpy(result->reason, "Invalid event kind. Must be valid integer.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_TAGS:
        strcpy(result->reason, "Invalid tags format. Tags must be array of string arrays.");
        break;
      case NOSTR_ERROR_EVENT_INVALID_CONTENT:
        strcpy(result->reason, "Invalid content format. Content must be valid string.");
        break;
      default:
        snprintf(result->reason, sizeof(result->reason),
                 "NOSTR event validation failed (error code: %d). Check event structure and format.",
                 validation_result);
        break;
    }
    
    cJSON_Delete(event);
    return NOSTR_SUCCESS;
  }

  // 11. Public Key Extraction (Both Paths)
  //     Extract validated public key for rule evaluation
  char extracted_pubkey[65] = {0};
  int extract_result = extract_pubkey_from_event(event, extracted_pubkey,
                                                 sizeof(extracted_pubkey));
  if (extract_result != NOSTR_SUCCESS) {
    char extract_msg[256];
    sprintf(extract_msg,
            "VALIDATOR_DEBUG: STEP 8 FAILED - Failed to extract pubkey from "
            "event (error=%d)\n",
            extract_result);

    result->valid = 0;
    result->error_code = extract_result;
    strcpy(result->reason, "Failed to extract public key from event. Pubkey must be 64-character hex string.");
    cJSON_Delete(event);
    return NOSTR_SUCCESS;
  }
  char extract_success_msg[256];
  sprintf(extract_success_msg,
          "VALIDATOR_DEBUG: STEP 8 PASSED - Extracted pubkey: %s\n",
          extracted_pubkey);


  /////////////////////////////////////////////////////////////////////
  //  EVENT KIND ROUTING - Dual Authentication Support
  /////////////////////////////////////////////////////////////////////
  //    Kind 22242 (NIP-42): Route to NIP-42 challenge validation
  //    Kind 24242 (Blossom): Route to Blossom operation validation
  //    Other Kinds: Skip Nostr validation, proceed to rules
  //    Invalid Kind: Reject immediately

  // Get event kind to determine authentication method
  cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
  int event_kind = 0;
  if (kind_json && cJSON_IsNumber(kind_json)) {
    event_kind = cJSON_GetNumberValue(kind_json);
  }
  char kind_msg[256];
  sprintf(kind_msg, "VALIDATOR_DEBUG: STEP 9 PASSED - Event kind: %d\n",
          event_kind);

  // Variable to store expected hash from Blossom events for Phase 4 validation
  char expected_hash_from_event[65] = {0};


  /////////////////////////////////////////////////////////////////////
  //  NIP42
  /////////////////////////////////////////////////////////////////////
  if (event_kind == NOSTR_NIP42_AUTH_EVENT_KIND) {
    // 8. NIP-42 Challenge Validation (Kind 22242 Only ~500μs)
    //    Validate relay tag, verify challenge tag, check expiration
    char nip42_msg[256];
    sprintf(nip42_msg,
            "VALIDATOR_DEBUG: STEP 10 - Processing NIP-42 authentication (kind "
            "%d)\n",
            NOSTR_NIP42_AUTH_EVENT_KIND);
    validator_debug_log(nip42_msg);

    // NIP-42 authentication (kind 22242)
    if (!request->nip42_enabled || g_auth_cache.nip42_mode == 0) {
      validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 "
                          "authentication is disabled\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_NIP42_DISABLED;
      strcpy(result->reason, "NIP-42 authentication is disabled");
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }

    // Extract challenge from event according to NIP-42 spec (tags only)
    const char *challenge_for_validation = request->challenge_id;
    if (!challenge_for_validation) {
      // Look for challenge in tags (NIP-42 spec compliant)
      cJSON *tags_json = cJSON_GetObjectItem(event, "tags");
      if (tags_json && cJSON_IsArray(tags_json)) {
        cJSON *tag = NULL;
        cJSON_ArrayForEach(tag, tags_json) {
          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, "challenge") == 0) {
            cJSON *challenge_value = cJSON_GetArrayItem(tag, 1);
            if (challenge_value && cJSON_IsString(challenge_value)) {
              const char *challenge_from_tag = cJSON_GetStringValue(challenge_value);
              if (challenge_from_tag && strlen(challenge_from_tag) > 0) {
                // NIP-42 doesn't specify a fixed length, so accept any reasonable length
                size_t challenge_len = strlen(challenge_from_tag);
                if (challenge_len >= 8 && challenge_len <= 128) {  // Reasonable bounds
                  // Basic validation - should be hex-like
                  int valid_chars = 1;
                  for (size_t i = 0; i < challenge_len; i++) {
                    char c = challenge_from_tag[i];
                    if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
                          (c >= 'A' && c <= 'F'))) {
                      valid_chars = 0;
                      break;
                    }
                  }
                  if (valid_chars) {
                    challenge_for_validation = challenge_from_tag;
                    char extract_msg[256];
                    sprintf(extract_msg,
                            "VALIDATOR_DEBUG: STEP 10 - Extracted challenge from event "
                            "tag: %.16s...\n",
                            challenge_for_validation);
                    validator_debug_log(extract_msg);
                    break;  // Found it, stop looking
                  }
                }
              }
            }
          }
        }
      }
    }

    if (!request->request_url || !challenge_for_validation) {
      validator_debug_log(
          "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 requires request_url and "
          "challenge (from event tags)\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_NIP42_INVALID_CHALLENGE;
      strcpy(result->reason, "NIP-42 authentication requires request_url and challenge in event tags");
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }

    int nip42_result = validate_nip42_event(event, request->request_url,
                                            challenge_for_validation);
    if (nip42_result != NOSTR_SUCCESS) {
      char nip42_fail_msg[256];
      sprintf(nip42_fail_msg,
              "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 validation failed "
              "(error=%d)\n",
              nip42_result);
      validator_debug_log(nip42_fail_msg);
      result->valid = 0;
      result->error_code = nip42_result;
      
      // Map specific NIP-42 error codes to detailed error messages
      switch (nip42_result) {
        case NOSTR_ERROR_NIP42_INVALID_CHALLENGE:
          strcpy(result->reason, "Challenge not found or invalid. Request a new challenge from /auth endpoint.");
          break;
        case NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED:
          strcpy(result->reason, "Challenge has expired. Request a new challenge from /auth endpoint.");
          break;
        case NOSTR_ERROR_NIP42_URL_MISMATCH:
          strcpy(result->reason, "Relay URL in auth event does not match server. Use 'ginxsom' as relay value.");
          break;
        case NOSTR_ERROR_NIP42_TIME_TOLERANCE:
          strcpy(result->reason, "Auth event timestamp is outside acceptable time window. Check system clock.");
          break;
        case NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID:
          strcpy(result->reason, "NIP-42 auth event structure is invalid. Verify event format and required tags.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
          strcpy(result->reason, "Event signature verification failed. Check private key and event serialization.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_CONTENT:
          strcpy(result->reason, "Event content is invalid. Challenge must be in event tags according to NIP-42.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_TAGS:
          strcpy(result->reason, "Required tags missing. Auth event must include 'relay' and 'expiration' tags.");
          break;
        default:
          snprintf(result->reason, sizeof(result->reason),
                   "NIP-42 authentication failed (error code: %d). Check event structure and signature.",
                   nip42_result);
          break;
      }
      
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }
    validator_debug_log(
        "VALIDATOR_DEBUG: STEP 10 PASSED - NIP-42 authentication succeeded\n");
    strcpy(result->reason, "NIP-42 authentication passed");

  } else if (event_kind == 24242) {
    // 10. Operation-Specific Validation (Kind 24242 Only)
    //     Verify operation authorization, check required tags, validate
    //     expiration Early exit: Expired or mismatched events rejected
    validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Blossom "
                        "authentication (kind 24242)\n");
    // Blossom protocol authentication (kind 24242) - ALWAYS validate kind 24242 events
    char blossom_valid_msg[512];
    sprintf(blossom_valid_msg,
            "VALIDATOR_DEBUG: Validating Blossom event for operation='%s', "
            "hash='%s'\n",
            request->operation ? request->operation : "NULL",
            request->resource_hash ? request->resource_hash : "NULL");
    validator_debug_log(blossom_valid_msg);

    int blossom_result = validate_blossom_event(event, request->resource_hash,
                                                request->operation);
    if (blossom_result != NOSTR_SUCCESS) {
      char blossom_fail_msg[256];
      sprintf(blossom_fail_msg,
              "VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed "
              "(error=%d)\n",
              blossom_result);
      validator_debug_log(blossom_fail_msg);
      result->valid = 0;
      result->error_code = blossom_result;
      
      // Map specific Blossom error codes to detailed error messages
      switch (blossom_result) {
        case NOSTR_ERROR_EVENT_EXPIRED:
          strcpy(result->reason, "Authorization event has expired. Create a new signed event with future expiration.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_CONTENT:
          strcpy(result->reason, "Event missing required tags. Blossom events need 't' (method) and 'x' tags.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_TAGS:
          strcpy(result->reason, "Invalid or missing Blossom tags. Check 't' tag matches operation and 'x' tag matches file hash.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
          strcpy(result->reason, "Event signature verification failed. Check private key and event serialization.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_KIND:
          strcpy(result->reason, "Invalid event kind. Blossom authorization events must use kind 24242.");
          break;
        default:
          snprintf(result->reason, sizeof(result->reason),
                   "Blossom event does not authorize this operation (error code: %d). Check tags and expiration.",
                   blossom_result);
          break;
      }
      
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }

    // Extract expected hash from event 'x' tag for Phase 4 use
    cJSON *tags = cJSON_GetObjectItem(event, "tags");
    if (tags && cJSON_IsArray(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, "x") == 0) {
          cJSON *hash_value = cJSON_GetArrayItem(tag, 1);
          if (hash_value && cJSON_IsString(hash_value)) {
            const char *event_hash = cJSON_GetStringValue(hash_value);
            if (event_hash && strlen(event_hash) == 64) {
              strncpy(expected_hash_from_event, event_hash, 64);
              expected_hash_from_event[64] = '\0';
              break;
            }
          }
        }
      }
    }
    
    validator_debug_log(
        "VALIDATOR_DEBUG: STEP 10 PASSED - Blossom authentication succeeded\n");
    strcpy(result->reason, "Blossom authentication passed");

  } else if (event_kind == 33335 || event_kind == 23459 || event_kind == 23458) {
    // 10. Admin/Configuration Event Validation (Kind 33335, 23459, 23458)
    //     Verify admin authorization, check required tags, validate expiration
    validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Admin/Configuration "
                        "authentication (kind 33335)\n");
    
    char admin_valid_msg[512];
    sprintf(admin_valid_msg,
            "VALIDATOR_DEBUG: Validating Admin event for operation='%s', "
            "endpoint='%s'\n",
            request->operation ? request->operation : "NULL",
            request->resource_hash ? request->resource_hash : "admin_api");
    validator_debug_log(admin_valid_msg);

    // For admin operations, we pass the HTTP method and API endpoint
    const char *admin_method = NULL;
    const char *admin_endpoint = NULL;
    
    // Extract method and endpoint from request context
    // For admin API, we need to get the actual HTTP method and full endpoint
    if (request->operation && strcmp(request->operation, "admin") == 0) {
      // Admin events should contain method and endpoint tags that match the actual request
      // We don't enforce specific values here - let the event tags drive the validation
      admin_method = NULL;  // Let validation check event tags without enforcing specific method
      admin_endpoint = NULL; // Let validation check event tags without enforcing specific endpoint
    }

    int admin_result = validate_admin_event(event, admin_method, admin_endpoint);
    if (admin_result != NOSTR_SUCCESS) {
      char admin_fail_msg[256];
      sprintf(admin_fail_msg,
              "VALIDATOR_DEBUG: STEP 10 FAILED - Admin validation failed "
              "(error=%d)\n",
              admin_result);
      validator_debug_log(admin_fail_msg);
      result->valid = 0;
      result->error_code = admin_result;
      
      // Map specific Admin error codes to detailed error messages
      switch (admin_result) {
        case NOSTR_ERROR_EVENT_EXPIRED:
          strcpy(result->reason, "Admin authorization event has expired. Create a new signed event with future expiration.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_CONTENT:
          strcpy(result->reason, "Admin event missing required tags. Admin events need 'method' and 'endpoint' tags.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_TAGS:
          strcpy(result->reason, "Invalid or missing admin tags. Check 'method' tag matches HTTP method and 'endpoint' tag is valid.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
          strcpy(result->reason, "Admin event signature verification failed. Check private key and event serialization.");
          break;
        case NOSTR_ERROR_EVENT_INVALID_KIND:
          strcpy(result->reason, "Invalid event kind. Admin authorization events must use kind 33335.");
          break;
        case NOSTR_ERROR_AUTH_REQUIRED:
          strcpy(result->reason, "Admin access denied. Pubkey is not authorized for admin operations.");
          break;
        default:
          snprintf(result->reason, sizeof(result->reason),
                   "Admin event does not authorize this operation (error code: %d). Check tags and admin permissions.",
                   admin_result);
          break;
      }
      
      cJSON_Delete(event);
      return NOSTR_SUCCESS;
    }
    
    validator_debug_log(
        "VALIDATOR_DEBUG: STEP 10 PASSED - Admin authentication succeeded\n");
    strcpy(result->reason, "Admin authentication passed");

  } else {
    char unsupported_msg[256];
    sprintf(unsupported_msg,
            "VALIDATOR_DEBUG: STEP 10 FAILED - Unsupported event kind: %d\n",
            event_kind);
    validator_debug_log(unsupported_msg);
    result->valid = 0;
    result->error_code = NOSTR_ERROR_EVENT_INVALID_KIND;
    snprintf(result->reason, sizeof(result->reason),
             "Unsupported event kind %d for authentication. Use kind 22242 for NIP-42, kind 24242 for Blossom, or kind 33335 for Admin.",
             event_kind);
    cJSON_Delete(event);
    return NOSTR_SUCCESS;
  }

  // Copy validated pubkey to result
  if (strlen(extracted_pubkey) == 64) {
    strncpy(result->pubkey, extracted_pubkey, 64);
    result->pubkey[64] = '\0';
    validator_debug_log(
        "VALIDATOR_DEBUG: STEP 11 PASSED - Pubkey copied to result\n");
  } else {
    char pubkey_warning_msg[256];
    sprintf(pubkey_warning_msg,
            "VALIDATOR_DEBUG: STEP 11 WARNING - Invalid pubkey length: %zu\n",
            strlen(extracted_pubkey));
    validator_debug_log(pubkey_warning_msg);
  }

  cJSON_Delete(event);

  // Skip rule evaluation for admin events
  if (event_kind == 33335 || event_kind == 23459 || event_kind == 23458) {
    char admin_skip_msg[256];
    snprintf(admin_skip_msg, sizeof(admin_skip_msg),
             "VALIDATOR_DEBUG: Admin event (kind %d) - skipping rule evaluation\n", event_kind);
    validator_debug_log(admin_skip_msg);
    strcpy(result->reason, "Admin event validated - rules bypassed");
    return NOSTR_SUCCESS;
  }

  // STEP 12 PASSED: Protocol validation complete - continue to database rule
  // evaluation
  validator_debug_log("VALIDATOR_DEBUG: STEP 12 PASSED - Protocol validation "
                      "complete, proceeding to rule evaluation\n");

  /////////////////////////////////////////////////////////////////////
  //  PHASE 3: AUTHENTICATION RULES (Database Queries ~500μs)
  /////////////////////////////////////////////////////////////////////
  // 12. Rules System Check - Quick config check if auth rules enabled
  //     Early exit: If disabled, allow request immediately

  // Check if authentication is disabled first (regardless of header presence)
  if (!g_auth_cache.auth_required) {
    validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication "
                        "disabled, allowing request\n");
    strcpy(result->reason, "Authentication disabled");
    return NOSTR_SUCCESS;
  }

  // 13. Cache Lookup - Check SQLite cache for previous decision
  //     Early exit: Cache hit returns cached decision (5-minute TTL ~100μs)

  /////////////////////////////////////////////////////////////////////
  //  RULE EVALUATION ENGINE (Priority Order)
  /////////////////////////////////////////////////////////////////////
  //     a. Pubkey Blacklist (highest priority) - Immediate denial if matched
  //     b. Hash Blacklist - Block specific content hashes
  //     c. MIME Type Blacklist - Block dangerous file types
  //     d. File Size Limits - Enforce upload size restrictions
  //     e. Pubkey Whitelist - Allow specific users (only if not denied above)
  //     f. MIME Type Whitelist - Allow specific file types

  validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - Auth rules enabled, "
                      "checking database rules\n");

  // Check database rules for authorization
  // For Blossom uploads, use hash from event 'x' tag instead of URI
  const char *hash_for_rules = request->resource_hash;
  if (event_kind == 24242 && strlen(expected_hash_from_event) == 64) {
      hash_for_rules = expected_hash_from_event;
      char hash_msg[256];
      sprintf(hash_msg, "VALIDATOR_DEBUG: Using hash from Blossom event for rules: %.16s...\n", hash_for_rules);
      validator_debug_log(hash_msg);
  }

  int rules_result = check_database_auth_rules(
      extracted_pubkey, request->operation, hash_for_rules, request->mime_type);
  if (rules_result != NOSTR_SUCCESS) {
    validator_debug_log(
        "VALIDATOR_DEBUG: STEP 14 FAILED - Database rules denied request\n");
    result->valid = 0;
    result->error_code = rules_result;
    // Determine specific failure reason based on rules evaluation
    if (rules_result == NOSTR_ERROR_AUTH_REQUIRED) {
      // This can be pubkey blacklist or whitelist violation - set generic
      // message The specific reason will be detailed in the database check
      // function
      strcpy(result->reason, "Request denied by authorization rules");
    } else {
      strcpy(result->reason, "Authorization error");
    }
    return NOSTR_SUCCESS;
  }
  validator_debug_log(
      "VALIDATOR_DEBUG: STEP 14 PASSED - Database rules allow request\n");

  // 15. Whitelist Default Denial - If whitelist rules exist but none matched,
  // deny
  //     Prevents whitelist bypass attacks

  // 16. Cache Storage - Store decision for future requests (5-minute TTL)

  /////////////////////////////////////////////////////////////////////
  //  PHASE 4: FILE CONTENT VALIDATION (Upload Operations Only ~100ms)
  /////////////////////////////////////////////////////////////////////
  // Only run expensive file processing AFTER all auth checks pass
  // This ensures blacklisted users never trigger file I/O

  if (event_kind == 24242 && request->operation &&
      strcmp(request->operation, "upload") == 0 &&
      strlen(expected_hash_from_event) == 64) {
    
    validator_debug_log("VALIDATOR_DEBUG: PHASE 4 - Starting file content validation for Blossom upload\n");
    
    char phase4_msg[256];
    sprintf(phase4_msg, "VALIDATOR_DEBUG: PHASE 4 - Expected hash: %.16s...\n", expected_hash_from_event);
    validator_debug_log(phase4_msg);

    // Get content length from request
    long content_length = request->file_size;
    if (content_length <= 0 || content_length > 100 * 1024 * 1024) { // 100MB limit
      validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Invalid content length\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
      strcpy(result->reason, "Invalid file size for upload validation");
      return NOSTR_SUCCESS;
    }

    // Allocate buffer for file data
    unsigned char *file_data = malloc(content_length);
    if (!file_data) {
      validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Memory allocation failed\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
      strcpy(result->reason, "Memory allocation failed for file validation");
      return NOSTR_SUCCESS;
    }
    
    // Read file data from stdin
    size_t bytes_read = fread(file_data, 1, content_length, stdin);
    if (bytes_read != (size_t)content_length) {
      free(file_data);
      validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Failed to read complete file data\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
      strcpy(result->reason, "Failed to read complete file data for validation");
      return NOSTR_SUCCESS;
    }
    
    char read_msg[256];
    sprintf(read_msg, "VALIDATOR_DEBUG: PHASE 4 - Read %zu bytes from stdin\n", bytes_read);
    validator_debug_log(read_msg);

    // Calculate SHA-256 hash of file content
    unsigned char hash_bytes[32];
    if (nostr_sha256(file_data, bytes_read, hash_bytes) != NOSTR_SUCCESS) {
      free(file_data);
      validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Hash calculation failed\n");
      result->valid = 0;
      result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
      strcpy(result->reason, "Failed to calculate file hash");
      return NOSTR_SUCCESS;
    }

    // Convert hash to hex string
    char calculated_hash[65];
    nostr_bytes_to_hex(hash_bytes, 32, calculated_hash);

    char hash_msg[256];
    sprintf(hash_msg, "VALIDATOR_DEBUG: PHASE 4 - Calculated hash: %.16s...\n", calculated_hash);
    validator_debug_log(hash_msg);

    // Compare hashes
    if (strcmp(calculated_hash, expected_hash_from_event) != 0) {
      free(file_data);
      validator_debug_log("VALIDATOR_DEBUG: PHASE 4 FAILED - Hash mismatch detected\n");
      char mismatch_msg[512];
      sprintf(mismatch_msg, "VALIDATOR_DEBUG: PHASE 4 - Expected: %.16s..., Got: %.16s...\n",
              expected_hash_from_event, calculated_hash);
      validator_debug_log(mismatch_msg);
      
      result->valid = 0;
      result->error_code = NOSTR_ERROR_AUTH_REQUIRED;
      strcpy(result->reason, "File hash mismatch. Uploaded file does not match hash in authorization event.");
      return NOSTR_SUCCESS;
    }
    
    // Hash matches - store file data in result for main handler to use
    result->file_data = file_data;
    result->file_size = bytes_read;
    result->owns_file_data = 1;
    strncpy(result->expected_hash, expected_hash_from_event, 64);
    result->expected_hash[64] = '\0';
    
    validator_debug_log("VALIDATOR_DEBUG: PHASE 4 PASSED - File hash validation successful, file data stored in result\n");
  }

  // All validations passed
  result->valid = 1;
  result->error_code = NOSTR_SUCCESS;
  validator_debug_log("VALIDATOR_DEBUG: STEP 15 PASSED - All validations "
                      "complete, request ALLOWED\n");
  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;
  memset(&g_auth_cache, 0, sizeof(g_auth_cache));
  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
//=============================================================================

/**
 * Get cache timeout from environment variable or default
 */
static int get_cache_timeout(void) {
  char *no_cache = getenv("GINX_NO_CACHE");
  char *cache_timeout = getenv("GINX_CACHE_TIMEOUT");

  if (no_cache && strcmp(no_cache, "1") == 0) {
    return 0; // No caching
  }

  if (cache_timeout) {
    int timeout = atoi(cache_timeout);
    return (timeout >= 0) ? timeout : 300; // Use provided value or default
  }

  return 300; // Default 5 minutes
}

/**
 * Force cache refresh - invalidates current cache
 */
void nostr_request_validator_force_cache_refresh(void) {
  g_auth_cache.cache_valid = 0;
  g_auth_cache.cache_expires = 0;
  validator_debug_log("VALIDATOR: Cache forcibly invalidated\n");
}

/**
 * Reload authentication configuration from unified config table
 */
static int reload_auth_config(void) {
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;
  int rc;

  // Clear cache
  memset(&g_auth_cache, 0, sizeof(g_auth_cache));

  // Open database
  rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
  if (rc != SQLITE_OK) {
    validator_debug_log("VALIDATOR: Could not open database\n");
    // Use defaults
    g_auth_cache.auth_required = 0;
    g_auth_cache.max_file_size = 104857600; // 100MB
    g_auth_cache.admin_enabled = 0;
    g_auth_cache.nip42_mode = 1; // Optional
    int cache_timeout = get_cache_timeout();
    g_auth_cache.cache_expires = time(NULL) + cache_timeout;
    g_auth_cache.cache_valid = 1;
    return NOSTR_SUCCESS;
  }

  // Load configuration values from unified config table
  const char *config_sql =
      "SELECT key, value FROM config WHERE key IN ('require_auth', "
      "'auth_rules_enabled', 'max_file_size', 'admin_enabled', 'admin_pubkey', "
      "'nip42_require_auth', 'nip42_challenge_timeout', "
      "'nip42_time_tolerance')";
  rc = sqlite3_prepare_v2(db, config_sql, -1, &stmt, NULL);

  if (rc == SQLITE_OK) {
    while (sqlite3_step(stmt) == SQLITE_ROW) {
      const char *key = (const char *)sqlite3_column_text(stmt, 0);
      const char *value = (const char *)sqlite3_column_text(stmt, 1);

      if (!key || !value)
        continue;

      if (strcmp(key, "require_auth") == 0) {
        g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0;
      } else if (strcmp(key, "auth_rules_enabled") == 0) {
        // Override auth_required with auth_rules_enabled if present (higher
        // priority)
        g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0;
      } else if (strcmp(key, "max_file_size") == 0) {
        g_auth_cache.max_file_size = atol(value);
      } else if (strcmp(key, "admin_enabled") == 0) {
        g_auth_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0;
      } else if (strcmp(key, "admin_pubkey") == 0) {
        strncpy(g_auth_cache.admin_pubkey, value,
                sizeof(g_auth_cache.admin_pubkey) - 1);
      } else if (strcmp(key, "nip42_require_auth") == 0) {
        if (strcmp(value, "false") == 0) {
          g_auth_cache.nip42_mode = 0; // Disabled
        } else if (strcmp(value, "required") == 0) {
          g_auth_cache.nip42_mode = 2; // Required
        } else if (strcmp(value, "true") == 0) {
          g_auth_cache.nip42_mode = 1; // Optional/Enabled
        } else {
          g_auth_cache.nip42_mode = 1; // Default to Optional/Enabled
        }
      } else if (strcmp(key, "nip42_challenge_timeout") == 0) {
        g_auth_cache.nip42_challenge_timeout = atoi(value);
      } else if (strcmp(key, "nip42_time_tolerance") == 0) {
        g_auth_cache.nip42_time_tolerance = atoi(value);
      }
    }
    sqlite3_finalize(stmt);
  }

  sqlite3_close(db);

  // Set cache expiration with environment variable support
  int cache_timeout = get_cache_timeout();
  g_auth_cache.cache_expires = time(NULL) + cache_timeout;
  g_auth_cache.cache_valid = 1;

  // Set defaults for missing values
  if (g_auth_cache.max_file_size == 0) {
    g_auth_cache.max_file_size = 104857600; // 100MB
  }

  // Debug logging
  app_log(LOG_DEBUG,
          "VALIDATOR: Configuration loaded from unified config table - "
          "auth_required: %d, max_file_size: %ld, nip42_mode: %d, "
          "cache_timeout: %d",
          g_auth_cache.auth_required, g_auth_cache.max_file_size,
          g_auth_cache.nip42_mode, cache_timeout);
  app_log(LOG_DEBUG,
          "VALIDATOR: NIP-42 mode details - nip42_mode=%d (0=disabled, "
          "1=optional/enabled, 2=required)",
          g_auth_cache.nip42_mode);

  return NOSTR_SUCCESS;
}

/**
 * Parse NOSTR authorization header (base64 decode)
 */
static int parse_authorization_header(const char *auth_header, char *event_json,
                                      size_t json_size) {
  if (!auth_header || !event_json) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Check for "Nostr " prefix (case-insensitive)
  const char *prefix = "nostr ";
  size_t prefix_len = strlen(prefix);

  if (strncasecmp(auth_header, prefix, prefix_len) != 0) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Extract base64 encoded event after "Nostr "
  const char *base64_event = auth_header + prefix_len;

  // Decode base64 to JSON using nostr_core_lib base64 decode
  unsigned char decoded_buffer[4096];
  size_t decoded_len = base64_decode(base64_event, decoded_buffer);

  if (decoded_len == 0 || decoded_len >= json_size) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Copy decoded JSON to output buffer
  memcpy(event_json, decoded_buffer, decoded_len);
  event_json[decoded_len] = '\0';

  return NOSTR_SUCCESS;
}

/**
 * Extract pubkey from validated NOSTR event
 */
static int extract_pubkey_from_event(cJSON *event, char *pubkey_buffer,
                                     size_t buffer_size) {
  if (!event || !pubkey_buffer || buffer_size < 65) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Initialize buffer to prevent corruption
  memset(pubkey_buffer, 0, buffer_size);

  cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
  if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }

  const char *pubkey = cJSON_GetStringValue(pubkey_json);
  if (!pubkey) {
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }

  // Check the raw pubkey string before validation
  size_t pubkey_len = strlen(pubkey);
  if (pubkey_len != 64) {
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }

  // Validate that pubkey contains only hex characters before copying
  for (int i = 0; i < 64; i++) {
    char c = pubkey[i];
    if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
          (c >= 'A' && c <= 'F'))) {
      return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
    }
  }

  // Safe copy with explicit length and null termination
  memcpy(pubkey_buffer, pubkey, 64);
  pubkey_buffer[64] = '\0';

  return NOSTR_SUCCESS;
}

/**
 * Validate Blossom protocol event (kind 24242)
 */
static int validate_blossom_event(cJSON *event, const char *expected_hash,
                                  const char *method) {
  if (!event) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Check event kind (must be 24242 for Blossom operations)
  cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
  if (!kind_json || !cJSON_IsNumber(kind_json)) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  int kind = cJSON_GetNumberValue(kind_json);
  if (kind != 24242) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // ALL Blossom events (kind 24242) must have proper tag structure
  cJSON *tags = cJSON_GetObjectItem(event, "tags");
  if (!tags || !cJSON_IsArray(tags)) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // Track what we find in the event tags
  int has_t_tag = 0;
  int has_x_tag = 0;
  int method_matches = (method == NULL); // If no expected method, consider it matched
  int hash_matches = (expected_hash == NULL); // If no expected hash, consider it matched
  time_t expiration = 0;

  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, "t") == 0) {
      has_t_tag = 1;
      cJSON *method_value = cJSON_GetArrayItem(tag, 1);
      if (method_value && cJSON_IsString(method_value)) {
        const char *event_method = cJSON_GetStringValue(method_value);
        if (method && strcmp(event_method, method) == 0) {
          method_matches = 1;
        }
      }
    } else if (strcmp(tag_name_str, "x") == 0) {
      has_x_tag = 1;
      cJSON *hash_value = cJSON_GetArrayItem(tag, 1);
      if (hash_value && cJSON_IsString(hash_value)) {
        const char *event_hash = cJSON_GetStringValue(hash_value);
        if (expected_hash && strcmp(event_hash, expected_hash) == 0) {
          hash_matches = 1;
        }
      }
    } else if (strcmp(tag_name_str, "expiration") == 0) {
      cJSON *exp_value = cJSON_GetArrayItem(tag, 1);
      if (exp_value && cJSON_IsString(exp_value)) {
        expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
      }
    }
  }

  // Blossom events MUST have both 't' and 'x' tags
  if (!has_t_tag || !has_x_tag) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // If we have expected values, they must match
  if (!method_matches || !hash_matches) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // Check expiration
  time_t now = time(NULL);
  if (expiration > 0 && now > expiration) {
    return NOSTR_ERROR_EVENT_EXPIRED;
  }

  return NOSTR_SUCCESS;
}

/**
 * Check database authentication rules for the request
 * Implements the 6-step rule evaluation engine from AUTH_API.md
 */
static int check_database_auth_rules(const char *pubkey, const char *operation,
                                      const char *resource_hash, const char *mime_type) {
  sqlite3 *db = NULL;
  sqlite3_stmt *stmt = NULL;
  int rc;
  int pubkey_whitelisted = 0;
  int pubkey_whitelist_exists = 0;
  int mime_whitelisted = 0;
  int mime_whitelist_exists = 0;
  int mime_whitelist_count = 0;
  int pubkey_whitelist_count = 0;
  char rules_msg[256];

  if (!pubkey) {
    validator_debug_log(
        "VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n");
    return NOSTR_ERROR_INVALID_INPUT;
  }

  if (operation && (strcmp(operation, "admin_event") == 0 ||
                    strcmp(operation, "admin") == 0)) {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Admin management request, skipping auth rules\n");
    return NOSTR_SUCCESS;
  }

  sprintf(rules_msg,
          "VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., "
          "operation=%s, mime_type=%s\n",
          pubkey, operation ? operation : "NULL", mime_type ? mime_type : "NULL");
  validator_debug_log(rules_msg);

  // Open database
  rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
  if (rc != SQLITE_OK) {
    validator_debug_log(
        "VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n");
    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 LIKE 'blacklist_pubkey' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
  rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) == SQLITE_ROW) {
      const char *description = "Pubkey blacklisted";
      validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - "
                          "Pubkey blacklisted\n");
      char blacklist_msg[256];
      sprintf(blacklist_msg,
              "VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n",
              description ? description : "Unknown");
      validator_debug_log(blacklist_msg);

      // Set specific violation details for status code mapping
      strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
      sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted",
              description ? description : "TEST_PUBKEY_BLACKLIST");

      sqlite3_finalize(stmt);
      sqlite3_close(db);
      return NOSTR_ERROR_AUTH_REQUIRED;
    }
    sqlite3_finalize(stmt);
  }
  validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey "
                      "not blacklisted\n");

  // Step 2: Check hash blacklist
  if (resource_hash) {
    const char *hash_blacklist_sql =
        "SELECT rule_type FROM auth_rules WHERE rule_type LIKE 'blacklist_hash' 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) {
        const char *description = "Hash blacklisted";
        validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - "
                            "Hash blacklisted\n");
        char hash_blacklist_msg[256];
        sprintf(
            hash_blacklist_msg,
            "VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n",
            description ? description : "Unknown");
        validator_debug_log(hash_blacklist_msg);

        // Set specific violation details for status code mapping
        strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
        sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted",
                description ? description : "TEST_HASH_BLACKLIST");

        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return NOSTR_ERROR_AUTH_REQUIRED;
      }
      sqlite3_finalize(stmt);
    }
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 PASSED - Hash "
                        "not blacklisted\n");
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 SKIPPED - No "
                        "resource hash provided\n");
  }

  // Step 3: Check MIME type blacklist
  if (mime_type) {
    // Match both exact MIME type and wildcard patterns (e.g., 'image/*')
    const char *mime_blacklist_sql =
        "SELECT rule_type FROM auth_rules WHERE rule_type LIKE 'blacklist_mime' AND pattern_type = 'mime' AND (pattern_value = ? OR pattern_value LIKE '%/*' AND ? LIKE REPLACE(pattern_value, '*', '%')) AND active = 1 LIMIT 1";
    rc = sqlite3_prepare_v2(db, mime_blacklist_sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
      sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC);
      sqlite3_bind_text(stmt, 2, mime_type, -1, SQLITE_STATIC);

      if (sqlite3_step(stmt) == SQLITE_ROW) {
        const char *description = "MIME type blacklisted";
        validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - "
                            "MIME type blacklisted\n");
        char mime_blacklist_msg[256];
        sprintf(
            mime_blacklist_msg,
            "VALIDATOR_DEBUG: RULES ENGINE - MIME blacklist rule matched: %s\n",
            description ? description : "Unknown");
        validator_debug_log(mime_blacklist_msg);

        // Set specific violation details for status code mapping
        strcpy(g_last_rule_violation.violation_type, "mime_blacklist");
        sprintf(g_last_rule_violation.reason, "%s: MIME type blacklisted",
                description ? description : "TEST_MIME_BLACKLIST");

        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return NOSTR_ERROR_AUTH_REQUIRED;
      }
      sqlite3_finalize(stmt);
    }
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - MIME "
                        "type not blacklisted\n");
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 SKIPPED - No "
                        "MIME type provided\n");
  }

  // Step 4: Check pubkey whitelist
  const char *whitelist_sql =
      "SELECT rule_type FROM auth_rules WHERE rule_type LIKE 'whitelist_pubkey' 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) {
      const char *description = "Pubkey whitelisted";
      validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - "
                          "Pubkey whitelisted\n");
      char whitelist_msg[256];
      snprintf(whitelist_msg,
               sizeof(whitelist_msg),
               "VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n",
               description ? description : "Unknown");
      validator_debug_log(whitelist_msg);
      pubkey_whitelisted = 1;
    } else {
      validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 - Pubkey not whitelisted\n");
    }
    sqlite3_finalize(stmt);
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 FAILED - Pubkey whitelist query failed\n");
  }

  // Step 5: Check MIME type whitelist
  if (mime_type) {
    char mime_pattern_wildcard[MAX_MIME_TYPE_LEN + 2];
    const char *mime_whitelist_sql =
        "SELECT rule_type FROM auth_rules WHERE rule_type LIKE 'whitelist_mime' AND pattern_type = 'mime' AND (pattern_value = ? OR pattern_value LIKE ? ) AND active = 1 LIMIT 1";
    rc = sqlite3_prepare_v2(db, mime_whitelist_sql, -1, &stmt, NULL);
    if (rc == SQLITE_OK) {
      sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC);
      const char *slash_pos = strchr(mime_type, '/');
      if (slash_pos != NULL) {
        size_t prefix_len = slash_pos - mime_type;
        if (prefix_len < MAX_MIME_TYPE_LEN) {
          snprintf(mime_pattern_wildcard, sizeof(mime_pattern_wildcard), "%.*s/%%", (int)prefix_len, mime_type);
        } else {
          snprintf(mime_pattern_wildcard, sizeof(mime_pattern_wildcard), "%%/%%");
        }
      } else {
        snprintf(mime_pattern_wildcard, sizeof(mime_pattern_wildcard), "%s/%%", mime_type);
      }
      sqlite3_bind_text(stmt, 2, mime_pattern_wildcard, -1, SQLITE_TRANSIENT);

      if (sqlite3_step(stmt) == SQLITE_ROW) {
        const char *description = "MIME type whitelisted";
        validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - MIME type whitelisted\n");
        char mime_whitelist_msg[256];
        snprintf(mime_whitelist_msg,
                 sizeof(mime_whitelist_msg),
                 "VALIDATOR_DEBUG: RULES ENGINE - MIME whitelist rule matched: %s (pattern=%s)\n",
                 description ? description : "Unknown",
                 mime_pattern_wildcard);
        validator_debug_log(mime_whitelist_msg);
        mime_whitelisted = 1;
      } else {
        char mime_not_msg[256];
        snprintf(mime_not_msg,
                 sizeof(mime_not_msg),
                 "VALIDATOR_DEBUG: RULES ENGINE - STEP 5 - MIME type not whitelisted (pattern=%s)\n",
                 mime_pattern_wildcard);
        validator_debug_log(mime_not_msg);
      }
      sqlite3_finalize(stmt);
    } else {
      validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 FAILED - Failed to prepare MIME whitelist query\n");
    }
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 SKIPPED - No MIME type provided\n");
  }

  // Step 6: Count MIME whitelist rules
  const char *mime_whitelist_exists_sql =
      "SELECT COUNT(*) FROM auth_rules WHERE rule_type LIKE 'whitelist_mime' "
      "AND pattern_type = 'mime' AND active = 1";
  rc = sqlite3_prepare_v2(db, mime_whitelist_exists_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    if (sqlite3_step(stmt) == SQLITE_ROW) {
      mime_whitelist_count = sqlite3_column_int(stmt, 0);
      char mime_cnt_msg[256];
      snprintf(mime_cnt_msg, sizeof(mime_cnt_msg),
               "VALIDATOR_DEBUG: RULES ENGINE - MIME whitelist count: %d\n",
               mime_whitelist_count);
      validator_debug_log(mime_cnt_msg);
    }
    sqlite3_finalize(stmt);
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 6 FAILED - Failed to prepare MIME whitelist count query\n");
  }

  if (mime_whitelist_count > 0 && !mime_whitelisted) {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - MIME whitelist exists but MIME type not allowed\n");
    strcpy(g_last_rule_violation.violation_type, "mime_whitelist_violation");
    strcpy(g_last_rule_violation.reason, "MIME type not whitelisted for this operation");
    sqlite3_close(db);
    return NOSTR_ERROR_AUTH_REQUIRED;
  }

  // Step 7: Count pubkey whitelist rules
  const char *whitelist_exists_sql =
      "SELECT COUNT(*) FROM auth_rules WHERE (rule_type LIKE 'whitelist_pubkey' OR rule_type LIKE 'pubkey_whitelist') "
      "AND pattern_type = 'pubkey' AND active = 1";
  rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
  if (rc == SQLITE_OK) {
    if (sqlite3_step(stmt) == SQLITE_ROW) {
      pubkey_whitelist_count = sqlite3_column_int(stmt, 0);
      char pubkey_cnt_msg[256];
      snprintf(pubkey_cnt_msg, sizeof(pubkey_cnt_msg),
               "VALIDATOR_DEBUG: RULES ENGINE - Pubkey whitelist count: %d\n",
               pubkey_whitelist_count);
      validator_debug_log(pubkey_cnt_msg);
    }
    sqlite3_finalize(stmt);
  } else {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 7 FAILED - Failed to prepare pubkey whitelist count query\n");
  }

  if (pubkey_whitelist_count > 0) {
    char pubkey_whitelist_msg[256];
    snprintf(pubkey_whitelist_msg, sizeof(pubkey_whitelist_msg),
             "VALIDATOR_DEBUG: RULES ENGINE - Pubkey whitelist exists (%d entries)\n",
             pubkey_whitelist_count);
    validator_debug_log(pubkey_whitelist_msg);
  }

  if (pubkey_whitelist_count > 0 && !pubkey_whitelisted) {
    validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Pubkey whitelist exists but pubkey not allowed\n");
    strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
    strcpy(g_last_rule_violation.reason, "Public key not whitelisted for this operation");
    sqlite3_close(db);
    return NOSTR_ERROR_AUTH_REQUIRED;
  }

  if ((mime_whitelist_count > 0 && !mime_whitelisted) ||
      (pubkey_whitelist_count > 0 && !pubkey_whitelisted)) {
    // Already handled above but include fallback
    sqlite3_close(db);
    return NOSTR_ERROR_AUTH_REQUIRED;
  }

  sqlite3_close(db);
  validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Completed whitelist checks\n");
  return NOSTR_SUCCESS;
}

/**
 * Validate NIP-42 authentication event (kind 22242)
 */
static int validate_nip42_event(cJSON *event, const char *relay_url,
                                const char *challenge_id) {
  if (!event || !relay_url || !challenge_id) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Check event kind (must be 22242 for NIP-42)
  cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
  if (!kind_json || !cJSON_IsNumber(kind_json)) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  int kind = cJSON_GetNumberValue(kind_json);
  if (kind != NOSTR_NIP42_AUTH_EVENT_KIND) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // Validate that the challenge exists and is not expired
  int challenge_result = validate_challenge(challenge_id);
  if (challenge_result != NOSTR_SUCCESS) {
    return challenge_result;
  }

  // Use the existing NIP-42 verification from nostr_core_lib
  int verification_result =
      nostr_nip42_verify_auth_event(event, challenge_id, relay_url,
                                    g_challenge_manager.time_tolerance_seconds);
  if (verification_result != NOSTR_SUCCESS) {
    return verification_result;
  }

  return NOSTR_SUCCESS;
}

/**
 * Validate Admin/Configuration event (kind 33335)
 */
static int validate_admin_event(cJSON *event, const char *method, const char *endpoint) {
  if (!event) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  // Check event kind (must be 33335 for Admin operations)
  cJSON *kind_json = cJSON_GetObjectItem(event, "kind");
  if (!kind_json || !cJSON_IsNumber(kind_json)) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  int kind = cJSON_GetNumberValue(kind_json);
  if (kind != 33335) {
    return NOSTR_ERROR_EVENT_INVALID_KIND;
  }

  // Get pubkey for admin authorization check
  cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey");
  if (!pubkey_json || !cJSON_IsString(pubkey_json)) {
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }
  
  const char *event_pubkey = cJSON_GetStringValue(pubkey_json);
  if (!event_pubkey || strlen(event_pubkey) != 64) {
    return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
  }

  // Check if pubkey is authorized for admin operations
  if (strlen(g_auth_cache.admin_pubkey) == 64) {
    if (strcmp(event_pubkey, g_auth_cache.admin_pubkey) != 0) {
      validator_debug_log("VALIDATOR_DEBUG: Admin pubkey mismatch - access denied\n");
      return NOSTR_ERROR_AUTH_REQUIRED;
    }
  } else {
    validator_debug_log("VALIDATOR_DEBUG: No admin pubkey configured - access denied\n");
    return NOSTR_ERROR_AUTH_REQUIRED;
  }

  // Validate admin event tag structure
  cJSON *tags = cJSON_GetObjectItem(event, "tags");
  if (!tags || !cJSON_IsArray(tags)) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // Track what we find in the event tags
  int has_method_tag = 0;
  int has_endpoint_tag = 0;
  int method_matches = (method == NULL); // If no expected method, consider it matched
  int endpoint_matches = (endpoint == NULL); // If no expected endpoint, consider it matched
  time_t expiration = 0;

  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, "method") == 0) {
      has_method_tag = 1;
      cJSON *method_value = cJSON_GetArrayItem(tag, 1);
      if (method_value && cJSON_IsString(method_value)) {
        const char *event_method = cJSON_GetStringValue(method_value);
        if (method && strcmp(event_method, method) == 0) {
          method_matches = 1;
        }
      }
    } else if (strcmp(tag_name_str, "endpoint") == 0) {
      has_endpoint_tag = 1;
      cJSON *endpoint_value = cJSON_GetArrayItem(tag, 1);
      if (endpoint_value && cJSON_IsString(endpoint_value)) {
        const char *event_endpoint = cJSON_GetStringValue(endpoint_value);
        // For endpoint matching, allow prefix matching for API endpoints
        if (endpoint && strncmp(event_endpoint, endpoint, strlen(endpoint)) == 0) {
          endpoint_matches = 1;
        }
      }
    } else if (strcmp(tag_name_str, "expiration") == 0) {
      cJSON *exp_value = cJSON_GetArrayItem(tag, 1);
      if (exp_value && cJSON_IsString(exp_value)) {
        expiration = (time_t)atol(cJSON_GetStringValue(exp_value));
      }
    }
  }

  // Admin events should have method and endpoint tags
  if (!has_method_tag || !has_endpoint_tag) {
    return NOSTR_ERROR_EVENT_INVALID_CONTENT;
  }

  // If we have expected values, they must match
  if (!method_matches || !endpoint_matches) {
    return NOSTR_ERROR_EVENT_INVALID_TAGS;
  }

  // Check expiration
  time_t now = time(NULL);
  if (expiration > 0 && now > expiration) {
    return NOSTR_ERROR_EVENT_EXPIRED;
  }

  validator_debug_log("VALIDATOR_DEBUG: Admin event validation passed\n");
  return NOSTR_SUCCESS;
}

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

  char cleanup_msg[256];
  sprintf(cleanup_msg, "NIP-42: Cleaned up challenges, %d active remaining\n",
          active_count);
  validator_debug_log(cleanup_msg);
}

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

  char store_msg[256];
  sprintf(store_msg,
          "NIP-42: Stored challenge %.16s... (expires in %d seconds)\n",
          challenge_id, g_challenge_manager.timeout_seconds);
  validator_debug_log(store_msg);

  return NOSTR_SUCCESS;
}

/**
 * Validate that a challenge exists and is not expired
 */
static int validate_challenge(const char *challenge_id) {
  if (!challenge_id || strlen(challenge_id) == 0) {
    return NOSTR_ERROR_INVALID_INPUT;
  }

  cleanup_expired_challenges();

  time_t now = time(NULL);

  for (int i = 0; i < g_challenge_manager.challenge_count; i++) {
    nip42_challenge_entry_t *entry = &g_challenge_manager.challenges[i];

    if (entry->active && strcmp(entry->challenge_id, challenge_id) == 0) {
      if (now <= entry->expires_at) {
        char validate_msg[256];
        sprintf(validate_msg,
                "NIP-42: Challenge %.16s... validated successfully\n",
                challenge_id);
        validator_debug_log(validate_msg);
        return NOSTR_SUCCESS;
      } else {
        // Mark as expired
        entry->active = 0;
        validator_debug_log("NIP-42: Challenge found but expired\n");
        return NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED;
      }
    }
  }

  validator_debug_log("NIP-42: Challenge not found\n");
  return NOSTR_ERROR_NIP42_INVALID_CHALLENGE;
}

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