/*
 * NOSTR Core Library - NIP-042: Authentication of clients to relays
 * 
 * Implements client authentication through signed ephemeral events
 */

#include "nip042.h"
#include "nip001.h"
#include "utils.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// Forward declarations for crypto functions
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);

// =============================================================================
// CLIENT-SIDE FUNCTIONS
// =============================================================================

/**
 * Create NIP-42 authentication event (kind 22242)
 */
cJSON* nostr_nip42_create_auth_event(const char* challenge, 
                                      const char* relay_url,
                                      const unsigned char* private_key,
                                      time_t timestamp) {
    if (!challenge || !relay_url || !private_key) {
        return NULL;
    }

    // Validate challenge format
    size_t challenge_len = strlen(challenge);
    if (challenge_len < NOSTR_NIP42_MIN_CHALLENGE_LENGTH || 
        challenge_len >= NOSTR_NIP42_MAX_CHALLENGE_LENGTH) {
        return NULL;
    }

    // Create tags array with relay and challenge
    cJSON* tags = cJSON_CreateArray();
    if (!tags) {
        return NULL;
    }

    // Add relay tag
    cJSON* relay_tag = cJSON_CreateArray();
    if (!relay_tag) {
        cJSON_Delete(tags);
        return NULL;
    }
    cJSON_AddItemToArray(relay_tag, cJSON_CreateString("relay"));
    cJSON_AddItemToArray(relay_tag, cJSON_CreateString(relay_url));
    cJSON_AddItemToArray(tags, relay_tag);

    // Add challenge tag
    cJSON* challenge_tag = cJSON_CreateArray();
    if (!challenge_tag) {
        cJSON_Delete(tags);
        return NULL;
    }
    cJSON_AddItemToArray(challenge_tag, cJSON_CreateString("challenge"));
    cJSON_AddItemToArray(challenge_tag, cJSON_CreateString(challenge));
    cJSON_AddItemToArray(tags, challenge_tag);

    // Create authentication event using existing function
    // Note: Empty content as per NIP-42 specification
    cJSON* auth_event = nostr_create_and_sign_event(
        NOSTR_NIP42_AUTH_EVENT_KIND,
        "",  // Empty content
        tags,
        private_key,
        timestamp
    );

    cJSON_Delete(tags);
    return auth_event;
}

/**
 * Create AUTH message JSON for relay communication
 */
char* nostr_nip42_create_auth_message(cJSON* auth_event) {
    if (!auth_event) {
        return NULL;
    }

    // Create AUTH message array: ["AUTH", <event-json>]
    cJSON* message_array = cJSON_CreateArray();
    if (!message_array) {
        return NULL;
    }

    cJSON_AddItemToArray(message_array, cJSON_CreateString("AUTH"));
    cJSON_AddItemToArray(message_array, cJSON_Duplicate(auth_event, 1));

    char* message_string = cJSON_PrintUnformatted(message_array);
    cJSON_Delete(message_array);

    return message_string;
}

/**
 * Validate challenge string format and freshness
 */
int nostr_nip42_validate_challenge(const char* challenge, 
                                   time_t received_at,
                                   int time_tolerance) {
    if (!challenge) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    size_t challenge_len = strlen(challenge);
    
    // Check challenge length
    if (challenge_len < NOSTR_NIP42_MIN_CHALLENGE_LENGTH) {
        return NOSTR_ERROR_NIP42_CHALLENGE_TOO_SHORT;
    }
    if (challenge_len >= NOSTR_NIP42_MAX_CHALLENGE_LENGTH) {
        return NOSTR_ERROR_NIP42_CHALLENGE_TOO_LONG;
    }

    // Check time validity if provided
    if (received_at > 0) {
        time_t now = time(NULL);
        int tolerance = (time_tolerance > 0) ? time_tolerance : NOSTR_NIP42_DEFAULT_TIME_TOLERANCE;
        
        if (now - received_at > tolerance) {
            return NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED;
        }
    }

    return NOSTR_SUCCESS;
}

/**
 * Parse AUTH challenge message from relay
 */
int nostr_nip42_parse_auth_challenge(const char* message,
                                     char* challenge_out,
                                     size_t challenge_size) {
    if (!message || !challenge_out || challenge_size == 0) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    cJSON* json = cJSON_Parse(message);
    if (!json || !cJSON_IsArray(json)) {
        if (json) cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Check array has exactly 2 elements
    if (cJSON_GetArraySize(json) != 2) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Check first element is "AUTH"
    cJSON* message_type = cJSON_GetArrayItem(json, 0);
    if (!message_type || !cJSON_IsString(message_type) || 
        strcmp(cJSON_GetStringValue(message_type), "AUTH") != 0) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Get challenge string
    cJSON* challenge_item = cJSON_GetArrayItem(json, 1);
    if (!challenge_item || !cJSON_IsString(challenge_item)) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    const char* challenge_str = cJSON_GetStringValue(challenge_item);
    if (!challenge_str || strlen(challenge_str) >= challenge_size) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_CHALLENGE;
    }

    strcpy(challenge_out, challenge_str);
    cJSON_Delete(json);

    return NOSTR_SUCCESS;
}

// =============================================================================
// SERVER-SIDE FUNCTIONS
// =============================================================================

/**
 * Generate cryptographically secure challenge string
 */
int nostr_nip42_generate_challenge(char* challenge_out, size_t length) {
    if (!challenge_out || length < NOSTR_NIP42_MIN_CHALLENGE_LENGTH || 
        length > NOSTR_NIP42_MAX_CHALLENGE_LENGTH / 2) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    // Generate random bytes
    unsigned char random_bytes[NOSTR_NIP42_MAX_CHALLENGE_LENGTH / 2];
    if (nostr_secp256k1_get_random_bytes(random_bytes, length) != 1) {
        return NOSTR_ERROR_CRYPTO_FAILED;
    }

    // Convert to hex string (reusing existing function)
    nostr_bytes_to_hex(random_bytes, length, challenge_out);

    return NOSTR_SUCCESS;
}

/**
 * Verify NIP-42 authentication event
 */
int nostr_nip42_verify_auth_event(cJSON* auth_event,
                                  const char* expected_challenge,
                                  const char* relay_url,
                                  int time_tolerance) {
    if (!auth_event || !expected_challenge || !relay_url) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    // First validate basic event structure using existing function
    int structure_result = nostr_validate_event_structure(auth_event);
    if (structure_result != NOSTR_SUCCESS) {
        return structure_result;
    }

    // Validate NIP-42 specific structure
    int nip42_structure_result = nostr_nip42_validate_auth_event_structure(
        auth_event, relay_url, expected_challenge, time_tolerance);
    if (nip42_structure_result != NOSTR_SUCCESS) {
        return nip42_structure_result;
    }

    // Finally verify cryptographic signature using existing function
    return nostr_verify_event_signature(auth_event);
}

/**
 * Parse AUTH message from client
 */
int nostr_nip42_parse_auth_message(const char* message, cJSON** auth_event_out) {
    if (!message || !auth_event_out) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    cJSON* json = cJSON_Parse(message);
    if (!json || !cJSON_IsArray(json)) {
        if (json) cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Check array has exactly 2 elements
    if (cJSON_GetArraySize(json) != 2) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Check first element is "AUTH"
    cJSON* message_type = cJSON_GetArrayItem(json, 0);
    if (!message_type || !cJSON_IsString(message_type) || 
        strcmp(cJSON_GetStringValue(message_type), "AUTH") != 0) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Get event object
    cJSON* event_item = cJSON_GetArrayItem(json, 1);
    if (!event_item || !cJSON_IsObject(event_item)) {
        cJSON_Delete(json);
        return NOSTR_ERROR_NIP42_INVALID_MESSAGE_FORMAT;
    }

    // Duplicate the event for the caller
    *auth_event_out = cJSON_Duplicate(event_item, 1);
    cJSON_Delete(json);

    if (!*auth_event_out) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }

    return NOSTR_SUCCESS;
}

/**
 * Create "auth-required" error response
 */
char* nostr_nip42_create_auth_required_message(const char* subscription_id,
                                               const char* event_id,
                                               const char* reason) {
    const char* default_reason = "authentication required";
    const char* message_reason = reason ? reason : default_reason;

    cJSON* response = cJSON_CreateArray();
    if (!response) {
        return NULL;
    }

    if (subscription_id) {
        // CLOSED message for subscriptions
        cJSON_AddItemToArray(response, cJSON_CreateString("CLOSED"));
        cJSON_AddItemToArray(response, cJSON_CreateString(subscription_id));
        
        char prefix_message[512];
        snprintf(prefix_message, sizeof(prefix_message), "auth-required: %s", message_reason);
        cJSON_AddItemToArray(response, cJSON_CreateString(prefix_message));
    } else if (event_id) {
        // OK message for events
        cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
        cJSON_AddItemToArray(response, cJSON_CreateString(event_id));
        cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // false
        
        char prefix_message[512];
        snprintf(prefix_message, sizeof(prefix_message), "auth-required: %s", message_reason);
        cJSON_AddItemToArray(response, cJSON_CreateString(prefix_message));
    } else {
        cJSON_Delete(response);
        return NULL;
    }

    char* message_string = cJSON_PrintUnformatted(response);
    cJSON_Delete(response);

    return message_string;
}

/**
 * Create "restricted" error response
 */
char* nostr_nip42_create_restricted_message(const char* subscription_id,
                                            const char* event_id,
                                            const char* reason) {
    const char* default_reason = "access restricted";
    const char* message_reason = reason ? reason : default_reason;

    cJSON* response = cJSON_CreateArray();
    if (!response) {
        return NULL;
    }

    if (subscription_id) {
        // CLOSED message for subscriptions
        cJSON_AddItemToArray(response, cJSON_CreateString("CLOSED"));
        cJSON_AddItemToArray(response, cJSON_CreateString(subscription_id));
        
        char prefix_message[512];
        snprintf(prefix_message, sizeof(prefix_message), "restricted: %s", message_reason);
        cJSON_AddItemToArray(response, cJSON_CreateString(prefix_message));
    } else if (event_id) {
        // OK message for events
        cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
        cJSON_AddItemToArray(response, cJSON_CreateString(event_id));
        cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // false
        
        char prefix_message[512];
        snprintf(prefix_message, sizeof(prefix_message), "restricted: %s", message_reason);
        cJSON_AddItemToArray(response, cJSON_CreateString(prefix_message));
    } else {
        cJSON_Delete(response);
        return NULL;
    }

    char* message_string = cJSON_PrintUnformatted(response);
    cJSON_Delete(response);

    return message_string;
}

// =============================================================================
// URL NORMALIZATION FUNCTIONS
// =============================================================================

/**
 * Normalize relay URL for comparison
 */
char* nostr_nip42_normalize_url(const char* url) {
    if (!url) {
        return NULL;
    }

    size_t url_len = strlen(url);
    char* normalized = malloc(url_len + 1);
    if (!normalized) {
        return NULL;
    }

    strcpy(normalized, url);

    // Remove trailing slash
    if (url_len > 1 && normalized[url_len - 1] == '/') {
        normalized[url_len - 1] = '\0';
    }

    // Convert to lowercase for domain comparison
    for (size_t i = 0; normalized[i]; i++) {
        if (normalized[i] >= 'A' && normalized[i] <= 'Z') {
            normalized[i] = normalized[i] + ('a' - 'A');
        }
    }

    return normalized;
}

/**
 * Check if two relay URLs match after normalization
 */
int nostr_nip42_urls_match(const char* url1, const char* url2) {
    if (!url1 || !url2) {
        return -1;
    }

    char* norm1 = nostr_nip42_normalize_url(url1);
    char* norm2 = nostr_nip42_normalize_url(url2);

    if (!norm1 || !norm2) {
        free(norm1);
        free(norm2);
        return -1;
    }

    int result = (strcmp(norm1, norm2) == 0) ? 1 : 0;

    free(norm1);
    free(norm2);

    return result;
}

// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================

/**
 * Get string description of authentication state
 */
const char* nostr_nip42_auth_state_str(nostr_auth_state_t state) {
    switch (state) {
        case NOSTR_AUTH_STATE_NONE:
            return "none";
        case NOSTR_AUTH_STATE_CHALLENGE_RECEIVED:
            return "challenge_received";
        case NOSTR_AUTH_STATE_AUTHENTICATING:
            return "authenticating";
        case NOSTR_AUTH_STATE_AUTHENTICATED:
            return "authenticated";
        case NOSTR_AUTH_STATE_REJECTED:
            return "rejected";
        default:
            return "unknown";
    }
}

/**
 * Initialize authentication context structure
 */
int nostr_nip42_init_auth_context(nostr_auth_context_t* ctx,
                                  const char* relay_url,
                                  const char* challenge,
                                  int time_tolerance) {
    if (!ctx || !relay_url || !challenge) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    memset(ctx, 0, sizeof(nostr_auth_context_t));

    ctx->relay_url = malloc(strlen(relay_url) + 1);
    if (!ctx->relay_url) {
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    strcpy(ctx->relay_url, relay_url);

    ctx->challenge = malloc(strlen(challenge) + 1);
    if (!ctx->challenge) {
        free(ctx->relay_url);
        ctx->relay_url = NULL;
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    strcpy(ctx->challenge, challenge);

    ctx->timestamp = time(NULL);
    ctx->time_tolerance = (time_tolerance > 0) ? time_tolerance : NOSTR_NIP42_DEFAULT_TIME_TOLERANCE;

    return NOSTR_SUCCESS;
}

/**
 * Free authentication context structure
 */
void nostr_nip42_free_auth_context(nostr_auth_context_t* ctx) {
    if (!ctx) {
        return;
    }

    free(ctx->relay_url);
    free(ctx->challenge);
    free(ctx->pubkey_hex);

    memset(ctx, 0, sizeof(nostr_auth_context_t));
}

/**
 * Validate authentication event structure (without signature verification)
 */
int nostr_nip42_validate_auth_event_structure(cJSON* auth_event,
                                              const char* relay_url,
                                              const char* challenge,
                                              int time_tolerance) {
    if (!auth_event || !relay_url || !challenge) {
        return NOSTR_ERROR_INVALID_INPUT;
    }

    // Check event kind is 22242
    cJSON* kind_item = cJSON_GetObjectItem(auth_event, "kind");
    if (!kind_item || !cJSON_IsNumber(kind_item) || 
        (int)cJSON_GetNumberValue(kind_item) != NOSTR_NIP42_AUTH_EVENT_KIND) {
        return NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID;
    }

    // Check timestamp is within tolerance
    cJSON* created_at_item = cJSON_GetObjectItem(auth_event, "created_at");
    if (!created_at_item || !cJSON_IsNumber(created_at_item)) {
        return NOSTR_ERROR_EVENT_INVALID_CREATED_AT;
    }

    time_t event_time = (time_t)cJSON_GetNumberValue(created_at_item);
    time_t now = time(NULL);
    int tolerance = (time_tolerance > 0) ? time_tolerance : NOSTR_NIP42_DEFAULT_TIME_TOLERANCE;
    
    if (abs((int)(now - event_time)) > tolerance) {
        return NOSTR_ERROR_NIP42_TIME_TOLERANCE;
    }

    // Check tags contain required relay and challenge
    cJSON* tags_item = cJSON_GetObjectItem(auth_event, "tags");
    if (!tags_item || !cJSON_IsArray(tags_item)) {
        return NOSTR_ERROR_EVENT_INVALID_TAGS;
    }

    int found_relay = 0, found_challenge = 0;

    cJSON* tag_item;
    cJSON_ArrayForEach(tag_item, tags_item) {
        if (!cJSON_IsArray(tag_item) || cJSON_GetArraySize(tag_item) < 2) {
            continue;
        }

        cJSON* tag_name = cJSON_GetArrayItem(tag_item, 0);
        cJSON* tag_value = cJSON_GetArrayItem(tag_item, 1);

        if (!cJSON_IsString(tag_name) || !cJSON_IsString(tag_value)) {
            continue;
        }

        const char* name = cJSON_GetStringValue(tag_name);
        const char* value = cJSON_GetStringValue(tag_value);

        if (strcmp(name, "relay") == 0) {
            if (nostr_nip42_urls_match(value, relay_url) == 1) {
                found_relay = 1;
            }
        } else if (strcmp(name, "challenge") == 0) {
            if (strcmp(value, challenge) == 0) {
                found_challenge = 1;
            }
        }
    }

    if (!found_relay) {
        return NOSTR_ERROR_NIP42_URL_MISMATCH;
    }

    if (!found_challenge) {
        return NOSTR_ERROR_NIP42_INVALID_CHALLENGE;
    }

    return NOSTR_SUCCESS;
}

// =============================================================================
// WEBSOCKET CLIENT INTEGRATION STUB FUNCTIONS
// =============================================================================
// Note: These will need to be implemented when WebSocket client structure is available

int nostr_ws_authenticate(struct nostr_ws_client* client,
                         const unsigned char* private_key,
                         int time_tolerance) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    (void)private_key;
    (void)time_tolerance;
    return NOSTR_ERROR_NETWORK_FAILED; // Placeholder
}

nostr_auth_state_t nostr_ws_get_auth_state(struct nostr_ws_client* client) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    return NOSTR_AUTH_STATE_NONE; // Placeholder
}

int nostr_ws_has_valid_challenge(struct nostr_ws_client* client) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    return 0; // Placeholder
}

int nostr_ws_get_challenge(struct nostr_ws_client* client,
                          char* challenge_out,
                          size_t challenge_size) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    (void)challenge_out;
    (void)challenge_size;
    return NOSTR_ERROR_NETWORK_FAILED; // Placeholder
}

int nostr_ws_store_challenge(struct nostr_ws_client* client,
                            const char* challenge) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    (void)challenge;
    return NOSTR_ERROR_NETWORK_FAILED; // Placeholder
}

int nostr_ws_clear_auth_state(struct nostr_ws_client* client) {
    // TODO: Implement when WebSocket client structure is available
    (void)client;
    return NOSTR_ERROR_NETWORK_FAILED; // Placeholder
}