/*
 * Ginxsom Admin Authentication Module
 * Handles Kind 23456/23457 admin events with NIP-44 encryption
 * Based on c-relay's dm_admin.c implementation
 */

#include "ginxsom.h"
#include "../nostr_core_lib/nostr_core/nostr_common.h"
#include "../nostr_core_lib/nostr_core/nip001.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include "../nostr_core_lib/nostr_core/utils.h"
#include <cjson/cJSON.h>
#include <sqlite3.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// Forward declarations
int get_blossom_private_key(char *seckey_out, size_t max_len);
int validate_admin_pubkey(const char *pubkey);

// Global variables for admin auth
static char g_blossom_seckey[65] = "";  // Cached blossom server private key
static int g_keys_loaded = 0;          // Whether keys have been loaded

// Load blossom server keys if not already loaded
static int ensure_keys_loaded(void) {
    if (!g_keys_loaded) {
        if (get_blossom_private_key(g_blossom_seckey, sizeof(g_blossom_seckey)) != 0) {
            app_log(LOG_ERROR, "ERROR: Cannot load blossom private key for admin auth\n");
            return -1;
        }
        g_keys_loaded = 1;
    }
    return 0;
}

// Validate that an event is a Kind 23456 admin command event
int is_admin_command_event(cJSON *event, const char *relay_pubkey) {
    if (!event || !relay_pubkey) {
        return 0;
    }

    // Check kind = 23456 (admin command)
    cJSON *kind = cJSON_GetObjectItem(event, "kind");
    if (!cJSON_IsNumber(kind) || kind->valueint != 23456) {
        return 0;
    }

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

    int found_p_tag = 0;
    cJSON *tag = NULL;
    cJSON_ArrayForEach(tag, tags) {
        if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
            cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
            cJSON *tag_value = cJSON_GetArrayItem(tag, 1);

            if (cJSON_IsString(tag_name) && strcmp(tag_name->valuestring, "p") == 0 &&
                cJSON_IsString(tag_value) && strcmp(tag_value->valuestring, relay_pubkey) == 0) {
                found_p_tag = 1;
                break;
            }
        }
    }

    return found_p_tag;
}

// Validate admin event signature and pubkey
int validate_admin_event(cJSON *event) {
    if (!event) {
        return 0;
    }

    // Get event fields
    cJSON *pubkey = cJSON_GetObjectItem(event, "pubkey");
    cJSON *sig = cJSON_GetObjectItem(event, "sig");

    if (!cJSON_IsString(pubkey) || !cJSON_IsString(sig)) {
        app_log(LOG_ERROR, "AUTH: Invalid event format - missing pubkey or sig\n");
        return 0;
    }

    // Check if pubkey matches configured admin pubkey
    if (!validate_admin_pubkey(pubkey->valuestring)) {
        app_log(LOG_ERROR, "AUTH: Pubkey %s is not authorized admin\n", pubkey->valuestring);
        return 0;
    }

    // TODO: Validate event signature using nostr_core_lib
    // For now, assume signature is valid if pubkey matches
    // In production, this should verify the signature cryptographically

    return 1;
}

// Decrypt NIP-44 encrypted admin command
int decrypt_admin_command(cJSON *event, char **decrypted_command_out) {
    if (!event || !decrypted_command_out) {
        return -1;
    }

    // Ensure we have the relay private key
    if (ensure_keys_loaded() != 0) {
        return -1;
    }

    // Get admin pubkey from event
    cJSON *admin_pubkey_json = cJSON_GetObjectItem(event, "pubkey");
    if (!cJSON_IsString(admin_pubkey_json)) {
        app_log(LOG_ERROR, "AUTH: Missing or invalid pubkey in event\n");
        return -1;
    }

    // Get encrypted content
    cJSON *content = cJSON_GetObjectItem(event, "content");
    if (!cJSON_IsString(content)) {
        app_log(LOG_ERROR, "AUTH: Missing or invalid content in event\n");
        return -1;
    }

    // Convert hex keys to bytes
    unsigned char blossom_private_key[32];
    unsigned char admin_public_key[32];
    
    if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key, 32) != 0) {
        app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key\n");
        return -1;
    }
    
    if (nostr_hex_to_bytes(admin_pubkey_json->valuestring, admin_public_key, 32) != 0) {
        app_log(LOG_ERROR, "AUTH: Failed to parse admin public key\n");
        return -1;
    }

    // Allocate buffer for decrypted content
    char decrypted_buffer[8192];
    
    // Decrypt using NIP-44
    int result = nostr_nip44_decrypt(
        blossom_private_key,
        admin_public_key,
        content->valuestring,
        decrypted_buffer,
        sizeof(decrypted_buffer)
    );
    
    if (result != NOSTR_SUCCESS) {
        app_log(LOG_ERROR, "AUTH: NIP-44 decryption failed with error code %d\n", result);
        return -1;
    }

    // Allocate and copy decrypted content
    *decrypted_command_out = malloc(strlen(decrypted_buffer) + 1);
    if (!*decrypted_command_out) {
        app_log(LOG_ERROR, "AUTH: Failed to allocate memory for decrypted content\n");
        return -1;
    }
    strcpy(*decrypted_command_out, decrypted_buffer);

    return 0;
}

// Parse decrypted command array
int parse_admin_command(const char *decrypted_content, char ***command_array_out, int *command_count_out) {
    if (!decrypted_content || !command_array_out || !command_count_out) {
        return -1;
    }

    // Parse the decrypted content as JSON array
    cJSON *content_json = cJSON_Parse(decrypted_content);
    if (!content_json) {
        app_log(LOG_ERROR, "AUTH: Failed to parse decrypted content as JSON\n");
        return -1;
    }

    if (!cJSON_IsArray(content_json)) {
        app_log(LOG_ERROR, "AUTH: Decrypted content is not a JSON array\n");
        cJSON_Delete(content_json);
        return -1;
    }

    int array_size = cJSON_GetArraySize(content_json);
    if (array_size < 1) {
        app_log(LOG_ERROR, "AUTH: Command array is empty\n");
        cJSON_Delete(content_json);
        return -1;
    }

    // Allocate command array
    char **command_array = malloc(array_size * sizeof(char *));
    if (!command_array) {
        app_log(LOG_ERROR, "AUTH: Failed to allocate command array\n");
        cJSON_Delete(content_json);
        return -1;
    }

    // Parse each array element as string
    for (int i = 0; i < array_size; i++) {
        cJSON *item = cJSON_GetArrayItem(content_json, i);
        if (!cJSON_IsString(item)) {
            app_log(LOG_ERROR, "AUTH: Command array element %d is not a string\n", i);
            // Clean up allocated strings
            for (int j = 0; j < i; j++) {
                free(command_array[j]);
            }
            free(command_array);
            cJSON_Delete(content_json);
            return -1;
        }

        command_array[i] = malloc(strlen(item->valuestring) + 1);
        if (!command_array[i]) {
            app_log(LOG_ERROR, "AUTH: Failed to allocate command string\n");
            // Clean up allocated strings
            for (int j = 0; j < i; j++) {
                free(command_array[j]);
            }
            free(command_array);
            cJSON_Delete(content_json);
            return -1;
        }
        strcpy(command_array[i], item->valuestring);
        if (!command_array[i]) {
            app_log(LOG_ERROR, "AUTH: Failed to duplicate command string\n");
            // Clean up allocated strings
            for (int j = 0; j < i; j++) {
                free(command_array[j]);
            }
            free(command_array);
            cJSON_Delete(content_json);
            return -1;
        }
    }

    cJSON_Delete(content_json);
    *command_array_out = command_array;
    *command_count_out = array_size;

    return 0;
}

// Process incoming admin command event (Kind 23456)
int process_admin_command(cJSON *event, char ***command_array_out, int *command_count_out, char **admin_pubkey_out) {
    if (!event || !command_array_out || !command_count_out || !admin_pubkey_out) {
        return -1;
    }

    // Get blossom server pubkey from config
    sqlite3 *db;
    sqlite3_stmt *stmt;
    char blossom_pubkey[65] = "";

    if (sqlite3_open_v2("db/ginxsom.db", &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) {
        return -1;
    }

    const char *sql = "SELECT value FROM config WHERE key = 'blossom_pubkey'";
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *pubkey = (const char *)sqlite3_column_text(stmt, 0);
            if (pubkey) {
                strncpy(blossom_pubkey, pubkey, sizeof(blossom_pubkey) - 1);
            }
        }
        sqlite3_finalize(stmt);
    }
    sqlite3_close(db);

    if (strlen(blossom_pubkey) != 64) {
        app_log(LOG_ERROR, "ERROR: Cannot determine blossom pubkey for admin auth\n");
        return -1;
    }

    // Check if it's a valid admin command event for us
    if (!is_admin_command_event(event, blossom_pubkey)) {
        return -1;
    }

    // Validate admin authentication (signature and pubkey)
    if (!validate_admin_event(event)) {
        return -1;
    }

    // Get admin pubkey from event
    cJSON *admin_pubkey_json = cJSON_GetObjectItem(event, "pubkey");
    if (!cJSON_IsString(admin_pubkey_json)) {
        return -1;
    }

    *admin_pubkey_out = malloc(strlen(admin_pubkey_json->valuestring) + 1);
    if (!*admin_pubkey_out) {
        app_log(LOG_ERROR, "AUTH: Failed to allocate admin pubkey string\n");
        return -1;
    }
    strcpy(*admin_pubkey_out, admin_pubkey_json->valuestring);
    if (!*admin_pubkey_out) {
        return -1;
    }

    // Decrypt the command
    char *decrypted_content = NULL;
    if (decrypt_admin_command(event, &decrypted_content) != 0) {
        free(*admin_pubkey_out);
        *admin_pubkey_out = NULL;
        return -1;
    }

    // Parse the command array
    if (parse_admin_command(decrypted_content, command_array_out, command_count_out) != 0) {
        free(decrypted_content);
        free(*admin_pubkey_out);
        *admin_pubkey_out = NULL;
        return -1;
    }

    free(decrypted_content);
    return 0;
}

// Validate admin pubkey against configured admin
int validate_admin_pubkey(const char *pubkey) {
    if (!pubkey || strlen(pubkey) != 64) {
        return 0;
    }

    sqlite3 *db;
    sqlite3_stmt *stmt;
    int result = 0;

    if (sqlite3_open_v2("db/ginxsom.db", &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) {
        return 0;
    }

    const char *sql = "SELECT value FROM config WHERE key = 'admin_pubkey'";
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *admin_pubkey = (const char *)sqlite3_column_text(stmt, 0);
            if (admin_pubkey && strcmp(admin_pubkey, pubkey) == 0) {
                result = 1;
            }
        }
        sqlite3_finalize(stmt);
    }
    sqlite3_close(db);

    return result;
}

// Create encrypted response for admin (Kind 23457)
int create_admin_response(const char *response_json, const char *admin_pubkey, const char *original_event_id __attribute__((unused)), cJSON **response_event_out) {
    if (!response_json || !admin_pubkey || !response_event_out) {
        return -1;
    }

    // Ensure we have the relay private key
    if (ensure_keys_loaded() != 0) {
        return -1;
    }

    // Get blossom server pubkey from config
    sqlite3 *db;
    sqlite3_stmt *stmt;
    char blossom_pubkey[65] = "";

    if (sqlite3_open_v2("db/ginxsom.db", &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) {
        return -1;
    }

    const char *sql = "SELECT value FROM config WHERE key = 'blossom_pubkey'";
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *pubkey = (const char *)sqlite3_column_text(stmt, 0);
            if (pubkey) {
                strncpy(blossom_pubkey, pubkey, sizeof(blossom_pubkey) - 1);
            }
        }
        sqlite3_finalize(stmt);
    }
    sqlite3_close(db);

    if (strlen(blossom_pubkey) != 64) {
        app_log(LOG_ERROR, "ERROR: Cannot determine blossom pubkey for response\n");
        return -1;
    }

    // Convert hex keys to bytes
    unsigned char blossom_private_key[32];
    unsigned char admin_public_key[32];
    
    if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key, 32) != 0) {
        app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key\n");
        return -1;
    }
    
    if (nostr_hex_to_bytes(admin_pubkey, admin_public_key, 32) != 0) {
        app_log(LOG_ERROR, "AUTH: Failed to parse admin public key\n");
        return -1;
    }

    // Encrypt response using NIP-44
    char encrypted_content[8192];
    int result = nostr_nip44_encrypt(
        blossom_private_key,
        admin_public_key,
        response_json,
        encrypted_content,
        sizeof(encrypted_content)
    );
    
    if (result != NOSTR_SUCCESS) {
        app_log(LOG_ERROR, "AUTH: NIP-44 encryption failed with error code %d\n", result);
        return -1;
    }

    // Create Kind 23457 response event
    cJSON *response_event = cJSON_CreateObject();
    if (!response_event) {
        app_log(LOG_ERROR, "AUTH: Failed to create response event JSON\n");
        return -1;
    }

    // Set event fields
    cJSON_AddNumberToObject(response_event, "kind", 23457);
    cJSON_AddStringToObject(response_event, "pubkey", blossom_pubkey);
    cJSON_AddNumberToObject(response_event, "created_at", (double)time(NULL));
    cJSON_AddStringToObject(response_event, "content", encrypted_content);

    // Add tags array with 'p' tag for admin
    cJSON *tags = cJSON_CreateArray();
    cJSON *p_tag = cJSON_CreateArray();
    cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
    cJSON_AddItemToArray(p_tag, cJSON_CreateString(admin_pubkey));
    cJSON_AddItemToArray(tags, p_tag);
    cJSON_AddItemToObject(response_event, "tags", tags);

    // Sign the event with blossom private key
    // Convert private key hex to bytes
    unsigned char blossom_private_key_bytes[32];
    if (nostr_hex_to_bytes(g_blossom_seckey, blossom_private_key_bytes, 32) != 0) {
        app_log(LOG_ERROR, "AUTH: Failed to parse blossom private key for signing\n");
        cJSON_Delete(response_event);
        return -1;
    }

    // Create a temporary event structure for signing
    cJSON* temp_event = cJSON_Duplicate(response_event, 1);
    if (!temp_event) {
        app_log(LOG_ERROR, "AUTH: Failed to create temp event for signing\n");
        cJSON_Delete(response_event);
        return -1;
    }

    // Sign the event using nostr_core_lib
    cJSON* signed_event = nostr_create_and_sign_event(
        23457,  // Kind 23457 (admin response)
        encrypted_content,  // content
        cJSON_GetObjectItem(response_event, "tags"),  // tags
        blossom_private_key_bytes,  // private key
        (time_t)cJSON_GetNumberValue(cJSON_GetObjectItem(response_event, "created_at"))  // timestamp
    );

    if (!signed_event) {
        app_log(LOG_ERROR, "AUTH: Failed to sign admin response event\n");
        cJSON_Delete(response_event);
        cJSON_Delete(temp_event);
        return -1;
    }

    // Extract id and signature from signed event
    cJSON* signed_id = cJSON_GetObjectItem(signed_event, "id");
    cJSON* signed_sig = cJSON_GetObjectItem(signed_event, "sig");

    if (signed_id && signed_sig) {
        cJSON_AddStringToObject(response_event, "id", cJSON_GetStringValue(signed_id));
        cJSON_AddStringToObject(response_event, "sig", cJSON_GetStringValue(signed_sig));
    } else {
        app_log(LOG_ERROR, "AUTH: Signed event missing id or sig\n");
        cJSON_Delete(response_event);
        cJSON_Delete(signed_event);
        cJSON_Delete(temp_event);
        return -1;
    }

    // Clean up temporary structures
    cJSON_Delete(signed_event);
    cJSON_Delete(temp_event);

    *response_event_out = response_event;
    return 0;
}

// Free command array allocated by parse_admin_command
void free_command_array(char **command_array, int command_count) {
    if (command_array) {
        for (int i = 0; i < command_count; i++) {
            if (command_array[i]) {
                free(command_array[i]);
            }
        }
        free(command_array);
    }
}