/*
 * NOSTR Core Library Implementation - Core Functionality
 * 
 * Self-contained crypto implementation (no external crypto dependencies)
 * 
 * This file contains:
 * - NIP-19: Bech32-encoded Entities
 * - NIP-01: Basic Protocol Flow
 * - NIP-06: Key Derivation from Mnemonic
 * - NIP-10: Text Notes (Kind 1)
 * - NIP-13: Proof of Work
 * - General Utilities
 * - Identity Management
 * - Single Relay Communication
 */

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L

#include "nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>

// Our self-contained crypto implementation
#include "nostr_crypto.h"

// Our production-ready WebSocket implementation
#include "../nostr_websocket/nostr_websocket_tls.h"

// cJSON for JSON handling
#include "../cjson/cJSON.h"


// Forward declarations for bech32 functions (used by NIP-06 functions)
static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad);
static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len);
static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len);


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                GENERAL UTILITIES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex) {
    for (size_t i = 0; i < len; i++) {
        sprintf(hex + i * 2, "%02x", bytes[i]);
    }
    hex[len * 2] = '\0';
}

int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len) {
    if (strlen(hex) != len * 2) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    for (size_t i = 0; i < len; i++) {
        if (sscanf(hex + i * 2, "%02hhx", &bytes[i]) != 1) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    }
    return NOSTR_SUCCESS;
}



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-01: BASIC PROTOCOL FLOW
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int nostr_init(void) {
    if (nostr_crypto_init() != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    return NOSTR_SUCCESS;
}

void nostr_cleanup(void) {
    nostr_crypto_cleanup();
}

const char* nostr_strerror(int error_code) {
    switch (error_code) {
        case NOSTR_SUCCESS: return "Success";
        case NOSTR_ERROR_INVALID_INPUT: return "Invalid input";
        case NOSTR_ERROR_CRYPTO_FAILED: return "Cryptographic operation failed";
        case NOSTR_ERROR_MEMORY_FAILED: return "Memory allocation failed";
        case NOSTR_ERROR_IO_FAILED: return "I/O operation failed";
        case NOSTR_ERROR_NETWORK_FAILED: return "Network operation failed";
        case NOSTR_ERROR_NIP04_INVALID_FORMAT: return "NIP-04 invalid format";
        case NOSTR_ERROR_NIP04_DECRYPT_FAILED: return "NIP-04 decryption failed";
        case NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL: return "NIP-04 buffer too small";
        default: return "Unknown error";
    }
}

cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp) {
    if (!private_key) {
        return NULL;
    }
    
    if (!content) {
        content = ""; // Default to empty content
    }
    
    // Convert private key to public key
    unsigned char public_key[32];
    if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
        return NULL;
    }
    
    // Convert public key to hex
    char pubkey_hex[65];
    nostr_bytes_to_hex(public_key, 32, pubkey_hex);
    
    // Create event structure
    cJSON* event = cJSON_CreateObject();
    if (!event) {
        return NULL;
    }
    
    // Use provided timestamp or current time if timestamp is 0
    time_t event_time = (timestamp == 0) ? time(NULL) : timestamp;
    
    cJSON_AddStringToObject(event, "pubkey", pubkey_hex);
    cJSON_AddNumberToObject(event, "created_at", (double)event_time);
    cJSON_AddNumberToObject(event, "kind", kind);
    
    // Add tags (copy provided tags or create empty array)
    if (tags) {
        cJSON_AddItemToObject(event, "tags", cJSON_Duplicate(tags, 1));
    } else {
        cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
    }
    
    cJSON_AddStringToObject(event, "content", content);
    
    // ============================================================================
    // INLINE SERIALIZATION AND SIGNING LOGIC 
    // ============================================================================
    
    // Get event fields for serialization
    cJSON* pubkey_item = cJSON_GetObjectItem(event, "pubkey");
    cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
    cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
    cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
    cJSON* content_item = cJSON_GetObjectItem(event, "content");
    
    if (!pubkey_item || !created_at_item || !kind_item || !tags_item || !content_item) {
        cJSON_Delete(event);
        return NULL;
    }
    
    // Create serialization array: [0, pubkey, created_at, kind, tags, content]
    cJSON* serialize_array = cJSON_CreateArray();
    if (!serialize_array) {
        cJSON_Delete(event);
        return NULL;
    }
    
    cJSON_AddItemToArray(serialize_array, cJSON_CreateNumber(0));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(pubkey_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(created_at_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(kind_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(tags_item, 1));
    cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(content_item, 1));
    
    char* serialize_string = cJSON_PrintUnformatted(serialize_array);
    cJSON_Delete(serialize_array);
    
    if (!serialize_string) {
        cJSON_Delete(event);
        return NULL;
    }
    
    // Hash the serialized event
    unsigned char event_hash[32];
    if (nostr_sha256((const unsigned char*)serialize_string, strlen(serialize_string), event_hash) != 0) {
        free(serialize_string);
        cJSON_Delete(event);
        return NULL;
    }
    
    // Convert hash to hex for event ID
    char event_id[65];
    nostr_bytes_to_hex(event_hash, 32, event_id);
    
    // Sign the hash using ECDSA
    unsigned char signature[64];
    if (nostr_ec_sign(private_key, event_hash, signature) != 0) {
        free(serialize_string);
        cJSON_Delete(event);
        return NULL;
    }
    
    // Convert signature to hex
    char sig_hex[129];
    nostr_bytes_to_hex(signature, 64, sig_hex);
    
    // Add ID and signature to the event
    cJSON_AddStringToObject(event, "id", event_id);
    cJSON_AddStringToObject(event, "sig", sig_hex);
    
    free(serialize_string);
    
    return event;
}



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-06: KEY DERIVATION FROM MNEMONIC
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key) {
    if (!private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Generate random entropy using /dev/urandom
    FILE* urandom = fopen("/dev/urandom", "rb");
    if (!urandom) {
        return NOSTR_ERROR_IO_FAILED;
    }
    
    if (fread(private_key, 1, 32, urandom) != 32) {
        fclose(urandom);
        return NOSTR_ERROR_IO_FAILED;
    }
    fclose(urandom);
    
    // Validate private key
    if (nostr_ec_private_key_verify(private_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Generate public key from private key (already x-only for NOSTR)
    if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size, 
                                    int account, unsigned char* private_key, 
                                    unsigned char* public_key) {
    if (!mnemonic || mnemonic_size < 256 || !private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Generate entropy for 12-word mnemonic
    unsigned char entropy[16];
    FILE* urandom = fopen("/dev/urandom", "rb");
    if (!urandom) {
        return NOSTR_ERROR_IO_FAILED;
    }
    
    if (fread(entropy, 1, sizeof(entropy), urandom) != sizeof(entropy)) {
        fclose(urandom);
        return NOSTR_ERROR_IO_FAILED;
    }
    fclose(urandom);
    
    // Generate mnemonic from entropy
    if (nostr_bip39_mnemonic_from_bytes(entropy, sizeof(entropy), mnemonic) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Derive keys from the generated mnemonic
    return nostr_derive_keys_from_mnemonic(mnemonic, account, private_key, public_key);
}

int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
                                   unsigned char* private_key, unsigned char* public_key) {
    if (!mnemonic || !private_key || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate mnemonic
    if (nostr_bip39_mnemonic_validate(mnemonic) != 0) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Convert mnemonic to seed
    unsigned char seed[64];
    if (nostr_bip39_mnemonic_to_seed(mnemonic, "", seed, sizeof(seed)) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Derive master key from seed
    nostr_hd_key_t master_key;
    if (nostr_bip32_key_from_seed(seed, sizeof(seed), &master_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // NIP-06 path: m/44'/1237'/account'/0/0
    nostr_hd_key_t derived_key;
    uint32_t path[] = {
        0x80000000 + 44,    // 44' (hardened)
        0x80000000 + 1237,  // 1237' (hardened)
        0x80000000 + account, // account' (hardened)
        0,                   // 0 (not hardened)
        0                    // 0 (not hardened)
    };
    
    if (nostr_bip32_derive_path(&master_key, path, sizeof(path) / sizeof(path[0]), &derived_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    // Extract private key and public key
    memcpy(private_key, derived_key.private_key, 32);
    memcpy(public_key, derived_key.public_key + 1, 32); // Remove compression prefix for x-only
    
    return NOSTR_SUCCESS;
}

int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output) {
    if (!key || !hrp || !output) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    uint8_t conv[64];
    size_t conv_len;
    
    if (!convert_bits(conv, &conv_len, 5, key, 32, 8, 1)) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    if (!bech32_encode(output, hrp, conv, conv_len)) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

nostr_input_type_t nostr_detect_input_type(const char* input) {
    if (!input || strlen(input) == 0) {
        return NOSTR_INPUT_UNKNOWN;
    }
    
    size_t len = strlen(input);
    
    // Check for bech32 nsec
    if (len > 5 && strncmp(input, "nsec1", 5) == 0) {
        return NOSTR_INPUT_NSEC_BECH32;
    }
    
    // Check for hex nsec (64 characters, all hex)
    if (len == 64) {
        int is_hex = 1;
        for (size_t i = 0; i < len; i++) {
            if (!isxdigit(input[i])) {
                is_hex = 0;
                break;
            }
        }
        if (is_hex) {
            return NOSTR_INPUT_NSEC_HEX;
        }
    }
    
    // Check for mnemonic (space-separated words)
    int word_count = 0;
    char temp[1024];
    strncpy(temp, input, sizeof(temp) - 1);
    temp[sizeof(temp) - 1] = '\0';
    
    char* token = strtok(temp, " ");
    while (token != NULL) {
        word_count++;
        token = strtok(NULL, " ");
    }
    
    // BIP39 mnemonics are typically 12, 18, or 24 words
    if (word_count >= 12 && word_count <= 24) {
        return NOSTR_INPUT_MNEMONIC;
    }
    
    return NOSTR_INPUT_UNKNOWN;
}

int nostr_decode_nsec(const char* input, unsigned char* private_key) {
    if (!input || !private_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    nostr_input_type_t type = nostr_detect_input_type(input);
    
    if (type == NOSTR_INPUT_NSEC_HEX) {
        if (nostr_hex_to_bytes(input, private_key, 32) != NOSTR_SUCCESS) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else if (type == NOSTR_INPUT_NSEC_BECH32) {
        size_t decoded_len;
        if (!bech32_decode(input, "nsec", private_key, &decoded_len)) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
        if (decoded_len != 32) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the private key
    if (nostr_ec_private_key_verify(private_key) != 0) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }
    
    return NOSTR_SUCCESS;
}

int nostr_decode_npub(const char* input, unsigned char* public_key) {
    if (!input || !public_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    nostr_input_type_t type = nostr_detect_input_type(input);
    
    if (type == NOSTR_INPUT_NSEC_HEX) {  // Actually public key hex
        if (nostr_hex_to_bytes(input, public_key, 32) != NOSTR_SUCCESS) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else if (strncmp(input, "npub1", 4) == 0) {  // Bech32 npub
        size_t decoded_len;
        if (!bech32_decode(input, "npub", public_key, &decoded_len)) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
        if (decoded_len != 32) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    } else {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the public key (could add nostr_ec_public_key_verify if it exists)
    
    return NOSTR_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-13: PROOF OF WORK
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/**
 * Count leading zero bits in a hash (NIP-13 reference implementation)
 */
static int zero_bits(unsigned char b) {
    int n = 0;
    
    if (b == 0)
        return 8;
    
    while (b >>= 1)
        n++;
    
    return 7-n;
}

/**
 * Find the number of leading zero bits in a hash (NIP-13 reference implementation)
 */
static int count_leading_zero_bits(unsigned char *hash) {
    int bits, total, i;
    for (i = 0, total = 0; i < 32; i++) {
        bits = zero_bits(hash[i]);
        total += bits;
        if (bits != 8)
            break;
    }
    return total;
}


/**
 * Add or update nonce tag with target difficulty
 */
static int update_nonce_tag_with_difficulty(cJSON* tags, uint64_t nonce, int target_difficulty) {
    if (!tags) return -1;
    
    // Look for existing nonce tag and remove it
    cJSON* tag = NULL;
    int index = 0;
    cJSON_ArrayForEach(tag, tags) {
        if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
            cJSON* tag_type = cJSON_GetArrayItem(tag, 0);
            if (tag_type && cJSON_IsString(tag_type) && 
                strcmp(cJSON_GetStringValue(tag_type), "nonce") == 0) {
                // Remove existing nonce tag
                cJSON_DetachItemFromArray(tags, index);
                cJSON_Delete(tag);
                break;
            }
        }
        index++;
    }
    
    // Add new nonce tag with format: ["nonce", "<nonce>", "<target_difficulty>"]
    cJSON* nonce_tag = cJSON_CreateArray();
    if (!nonce_tag) return -1;
    
    char nonce_str[32];
    char difficulty_str[16];
    snprintf(nonce_str, sizeof(nonce_str), "%llu", (unsigned long long)nonce);
    snprintf(difficulty_str, sizeof(difficulty_str), "%d", target_difficulty);
    
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString("nonce"));
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(nonce_str));
    cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(difficulty_str));
    
    cJSON_AddItemToArray(tags, nonce_tag);
    return 0;
}

/**
 * Helper function to replace event content with successful PoW result
 */
static void replace_event_content(cJSON* target_event, cJSON* source_event) {
    // Remove old fields
    cJSON_DeleteItemFromObject(target_event, "id");
    cJSON_DeleteItemFromObject(target_event, "sig");
    cJSON_DeleteItemFromObject(target_event, "tags");
    cJSON_DeleteItemFromObject(target_event, "created_at");
    
    // Copy new fields from successful event
    cJSON* id = cJSON_GetObjectItem(source_event, "id");
    cJSON* sig = cJSON_GetObjectItem(source_event, "sig");
    cJSON* tags = cJSON_GetObjectItem(source_event, "tags");
    cJSON* created_at = cJSON_GetObjectItem(source_event, "created_at");
    
    if (id) cJSON_AddItemToObject(target_event, "id", cJSON_Duplicate(id, 1));
    if (sig) cJSON_AddItemToObject(target_event, "sig", cJSON_Duplicate(sig, 1));
    if (tags) cJSON_AddItemToObject(target_event, "tags", cJSON_Duplicate(tags, 1));
    if (created_at) cJSON_AddItemToObject(target_event, "created_at", cJSON_Duplicate(created_at, 1));
}

/**
 * Add NIP-13 Proof of Work to an event
 * 
 * @param event The event to add proof of work to
 * @param private_key The private key for re-signing the event
 * @param target_difficulty Target number of leading zero bits (default: 4 if 0)
 * @param progress_callback Optional callback for mining progress
 * @param user_data User data for progress callback
 * @return NOSTR_SUCCESS on success, error code on failure
 */
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key, 
                           int target_difficulty,
                           void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
                           void* user_data) {
    if (!event || !private_key) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Set default difficulty if not specified (but allow 0 to disable PoW)
    if (target_difficulty < 0) {
        target_difficulty = 4;
    }
    
    // If target_difficulty is 0, skip proof of work entirely
    if (target_difficulty == 0) {
        return NOSTR_SUCCESS;
    }
    
    // Extract event data for reconstruction
    cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
    cJSON* content_item = cJSON_GetObjectItem(event, "content");
    cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
    cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
    
    if (!kind_item || !content_item || !created_at_item || !tags_item) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    int kind = (int)cJSON_GetNumberValue(kind_item);
    const char* content = cJSON_GetStringValue(content_item);
    time_t original_timestamp = (time_t)cJSON_GetNumberValue(created_at_item);
    
    uint64_t nonce = 0;
    int attempts = 0;
    int max_attempts = 10000000;
    time_t current_timestamp = original_timestamp;
    
    // PoW difficulty tracking variables
    int best_difficulty_this_round = 0;
    int best_difficulty_overall = 0;
    
    // Mining loop
    while (attempts < max_attempts) {
        // Update timestamp every 10,000 iterations
        if (attempts % 10000 == 0) {
            current_timestamp = time(NULL);
#ifdef ENABLE_DEBUG_LOGGING
            FILE* f = fopen("debug.log", "a"); 
            if (f) { 
                fprintf(f, "PoW mining: %d attempts, best this round: %d, overall best: %d, goal: %d\n", 
                        attempts, best_difficulty_this_round, best_difficulty_overall, target_difficulty); 
                fclose(f); 
            }
#endif
            // Reset best difficulty for the new round
            best_difficulty_this_round = 0;
        }
        
        // Create working copy of tags and add nonce
        cJSON* working_tags = cJSON_Duplicate(tags_item, 1);
        if (!working_tags) {
            return NOSTR_ERROR_MEMORY_FAILED;
        }
        
        if (update_nonce_tag_with_difficulty(working_tags, nonce, target_difficulty) != 0) {
            cJSON_Delete(working_tags);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Create and sign new event with current nonce
        cJSON* test_event = nostr_create_and_sign_event(kind, content, working_tags, 
                                                       private_key, current_timestamp);
        cJSON_Delete(working_tags);
        
        if (!test_event) {
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Check PoW difficulty
        cJSON* id_item = cJSON_GetObjectItem(test_event, "id");
        if (!id_item || !cJSON_IsString(id_item)) {
            cJSON_Delete(test_event);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        const char* event_id = cJSON_GetStringValue(id_item);
        unsigned char hash[32];
        if (nostr_hex_to_bytes(event_id, hash, 32) != NOSTR_SUCCESS) {
            cJSON_Delete(test_event);
            return NOSTR_ERROR_CRYPTO_FAILED;
        }
        
        // Count leading zero bits using NIP-13 method
        int current_difficulty = count_leading_zero_bits(hash);
        
        // Update difficulty tracking
        if (current_difficulty > best_difficulty_this_round) {
            best_difficulty_this_round = current_difficulty;
        }
        if (current_difficulty > best_difficulty_overall) {
            best_difficulty_overall = current_difficulty;
        }
        
        // Call progress callback if provided
        if (progress_callback) {
            progress_callback(current_difficulty, nonce, user_data);
        }
        
        // Check if we've reached the target
        if (current_difficulty >= target_difficulty) {
#ifdef ENABLE_DEBUG_LOGGING
            FILE* f = fopen("debug.log", "a"); 
            if (f) { 
                fprintf(f, "PoW SUCCESS: Found difficulty %d (target %d) at nonce %llu after %d attempts\n", 
                        current_difficulty, target_difficulty, (unsigned long long)nonce, attempts + 1);
                
                // Print the final event JSON
                char* event_json = cJSON_Print(test_event);
                if (event_json) {
                    fprintf(f, "Final event: %s\n", event_json);
                    free(event_json);
                }
                fclose(f); 
            }
#endif
            
            // Copy successful result back to input event
            replace_event_content(event, test_event);
            cJSON_Delete(test_event);
            return NOSTR_SUCCESS;
        }
        
        cJSON_Delete(test_event);
        nonce++;
        attempts++;
    }
    
#ifdef ENABLE_DEBUG_LOGGING
    // Debug logging - failure
    FILE* f = fopen("debug.log", "a"); 
    if (f) { 
        fprintf(f, "PoW FAILED: Mining failed after %d attempts\n", max_attempts); 
        fclose(f); 
    }
#endif
    
    // If we reach here, we've exceeded max attempts
    return NOSTR_ERROR_CRYPTO_FAILED;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//                NIP-19: BECH32-ENCODED ENTITIES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#define BECH32_CONST 1

static const char bech32_charset[] = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

static const int8_t bech32_charset_rev[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    15, -1, 10, 17, 21, 20, 26, 30,  7,  5, -1, -1, -1, -1, -1, -1,
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1,
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1
};

static uint32_t bech32_polymod_step(uint32_t pre) {
    uint8_t b = pre >> 25;
    return ((pre & 0x1FFFFFF) << 5) ^
           (-((b >> 0) & 1) & 0x3b6a57b2UL) ^
           (-((b >> 1) & 1) & 0x26508e6dUL) ^
           (-((b >> 2) & 1) & 0x1ea119faUL) ^
           (-((b >> 3) & 1) & 0x3d4233ddUL) ^
           (-((b >> 4) & 1) & 0x2a1462b3UL);
}

static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad) {
    uint32_t val = 0;
    int bits = 0;
    uint32_t maxv = (((uint32_t)1) << outbits) - 1;
    *outlen = 0;
    while (inlen--) {
        val = (val << inbits) | *(in++);
        bits += inbits;
        while (bits >= outbits) {
            bits -= outbits;
            out[(*outlen)++] = (val >> bits) & maxv;
        }
    }
    if (pad) {
        if (bits) {
            out[(*outlen)++] = (val << (outbits - bits)) & maxv;
        }
    } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) {
        return 0;
    }
    return 1;
}

static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len) {
    uint32_t chk = 1;
    size_t i, hrp_len = strlen(hrp);
    
    for (i = 0; i < hrp_len; ++i) {
        int ch = hrp[i];
        if (ch < 33 || ch > 126) return 0;
        if (ch >= 'A' && ch <= 'Z') return 0;
        chk = bech32_polymod_step(chk) ^ (ch >> 5);
    }
    
    chk = bech32_polymod_step(chk);
    for (i = 0; i < hrp_len; ++i) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
        *(output++) = hrp[i];
    }
    
    *(output++) = '1';
    for (i = 0; i < data_len; ++i) {
        if (*data >> 5) return 0;
        chk = bech32_polymod_step(chk) ^ (*data);
        *(output++) = bech32_charset[*(data++)];
    }
    
    for (i = 0; i < 6; ++i) {
        chk = bech32_polymod_step(chk);
    }
    
    chk ^= BECH32_CONST;
    for (i = 0; i < 6; ++i) {
        *(output++) = bech32_charset[(chk >> ((5 - i) * 5)) & 0x1f];
    }
    
    *output = 0;
    return 1;
}

static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len) {
    if (!input || !hrp || !data || !data_len) {
        return 0;
    }
    
    size_t input_len = strlen(input);
    size_t hrp_len = strlen(hrp);
    
    if (input_len < hrp_len + 7) return 0;
    if (strncmp(input, hrp, hrp_len) != 0) return 0;
    if (input[hrp_len] != '1') return 0;
    
    const char* data_part = input + hrp_len + 1;
    size_t data_part_len = input_len - hrp_len - 1;
    
    uint8_t values[256];
    for (size_t i = 0; i < data_part_len; i++) {
        unsigned char c = (unsigned char)data_part[i];
        if (c >= 128) return 0;
        int8_t val = bech32_charset_rev[c];
        if (val == -1) return 0;
        values[i] = (uint8_t)val;
    }
    
    if (data_part_len < 6) return 0;
    
    uint32_t chk = 1;
    for (size_t i = 0; i < hrp_len; i++) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] >> 5);
    }
    chk = bech32_polymod_step(chk);
    for (size_t i = 0; i < hrp_len; i++) {
        chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
    }
    for (size_t i = 0; i < data_part_len; i++) {
        chk = bech32_polymod_step(chk) ^ values[i];
    }
    
    if (chk != BECH32_CONST) return 0;
    
    size_t payload_len = data_part_len - 6;
    size_t decoded_len;
    if (!convert_bits(data, &decoded_len, 8, values, payload_len, 5, 0)) {
        return 0;
    }
    
    *data_len = decoded_len;
    return 1;
}
