/*
 * NOSTR Core Library - NIP-005: Mapping Nostr keys to DNS-based internet identifiers
 */

#include "nip005.h"
#include "../cjson/cJSON.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>  // For strcasecmp
#include <ctype.h>


#include <curl/curl.h>


// Structure for HTTP response handling
typedef struct {
    char* data;
    size_t size;
    size_t capacity;
} nip05_http_response_t;

/**
 * Callback function for curl to write HTTP response data
 */
static size_t nip05_write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
    nip05_http_response_t* response = (nip05_http_response_t*)userp;
    size_t total_size = size * nmemb;
    
    // Check if we need to expand the buffer
    if (response->size + total_size >= response->capacity) {
        size_t new_capacity = response->capacity * 2;
        if (new_capacity < response->size + total_size + 1) {
            new_capacity = response->size + total_size + 1;
        }
        
        char* new_data = realloc(response->data, new_capacity);
        if (!new_data) {
            return 0; // Out of memory
        }
        
        response->data = new_data;
        response->capacity = new_capacity;
    }
    
    // Append the new data
    memcpy(response->data + response->size, contents, total_size);
    response->size += total_size;
    response->data[response->size] = '\0';
    
    return total_size;
}

/**
 * Parse and validate a NIP-05 identifier into local part and domain
 */
static int nip05_parse_identifier(const char* identifier, char* local_part, char* domain) {
    if (!identifier || !local_part || !domain) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Find the @ symbol
    const char* at_pos = strchr(identifier, '@');
    if (!at_pos) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    // Extract local part
    size_t local_len = at_pos - identifier;
    if (local_len == 0 || local_len >= 64) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    strncpy(local_part, identifier, local_len);
    local_part[local_len] = '\0';
    
    // Extract domain
    const char* domain_start = at_pos + 1;
    size_t domain_len = strlen(domain_start);
    if (domain_len == 0 || domain_len >= 256) {
        return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
    }
    
    strcpy(domain, domain_start);
    
    // Validate characters in local part (a-z0-9-_.)
    for (size_t i = 0; i < local_len; i++) {
        char c = local_part[i];
        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 
              (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.')) {
            return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
        }
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Make HTTP GET request to a URL
 */
static int nip05_http_get(const char* url, int timeout_seconds, char** response_data) {
    if (!url || !response_data) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    CURL* curl = curl_easy_init();
    if (!curl) {
        return NOSTR_ERROR_NETWORK_FAILED;
    }
    
    nip05_http_response_t response = {0};
    response.capacity = 1024;
    response.data = malloc(response.capacity);
    if (!response.data) {
        curl_easy_cleanup(curl);
        return NOSTR_ERROR_MEMORY_FAILED;
    }
    response.data[0] = '\0';
    
    // Set curl options with proper type casting to fix warnings
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)nip05_write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP05_DEFAULT_TIMEOUT));
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // NIP-05 forbids redirects
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "nostr-core/1.0");
    
    // Perform the request
    CURLcode res = curl_easy_perform(curl);
    long response_code = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
    
    curl_easy_cleanup(curl);
    
    if (res != CURLE_OK) {
        free(response.data);
        return NOSTR_ERROR_NIP05_HTTP_FAILED;
    }
    
    if (response_code != 200) {
        free(response.data);
        return NOSTR_ERROR_NIP05_HTTP_FAILED;
    }
    
    *response_data = response.data;
    return NOSTR_SUCCESS;
}

/**
 * Validate that a hex string is a valid public key
 */
static int nip05_validate_pubkey_hex(const char* hex_pubkey) {
    if (!hex_pubkey) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    size_t len = strlen(hex_pubkey);
    if (len != 64) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Check all characters are valid hex
    for (size_t i = 0; i < len; i++) {
        char c = hex_pubkey[i];
        if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
            return NOSTR_ERROR_INVALID_INPUT;
        }
    }
    
    return NOSTR_SUCCESS;
}

/**
 * Parse a .well-known/nostr.json response and extract pubkey and relays for a specific name
 */
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
                                 char* pubkey_hex_out, char*** relays, int* relay_count) {
    if (!json_response || !local_part) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Initialize outputs
    if (pubkey_hex_out) {
        pubkey_hex_out[0] = '\0';
    }
    if (relays) {
        *relays = NULL;
    }
    if (relay_count) {
        *relay_count = 0;
    }
    
    // Parse JSON
    cJSON* json = cJSON_Parse(json_response);
    if (!json) {
        return NOSTR_ERROR_NIP05_JSON_PARSE_FAILED;
    }
    
    int result = NOSTR_ERROR_NIP05_NAME_NOT_FOUND;
    
    // Get the "names" object
    cJSON* names = cJSON_GetObjectItem(json, "names");
    if (names && cJSON_IsObject(names)) {
        cJSON* pubkey_item = cJSON_GetObjectItem(names, local_part);
        if (pubkey_item && cJSON_IsString(pubkey_item)) {
            const char* found_pubkey = cJSON_GetStringValue(pubkey_item);
            
            // Validate the public key format
            if (nip05_validate_pubkey_hex(found_pubkey) == NOSTR_SUCCESS) {
                if (pubkey_hex_out) {
                    strcpy(pubkey_hex_out, found_pubkey);
                }
                result = NOSTR_SUCCESS;
                
                // Extract relays if requested
                if (relays && relay_count) {
                    cJSON* relays_obj = cJSON_GetObjectItem(json, "relays");
                    if (relays_obj && cJSON_IsObject(relays_obj)) {
                        cJSON* user_relays = cJSON_GetObjectItem(relays_obj, found_pubkey);
                        if (user_relays && cJSON_IsArray(user_relays)) {
                            int relay_array_size = cJSON_GetArraySize(user_relays);
                            if (relay_array_size > 0) {
                                char** relay_array = malloc(relay_array_size * sizeof(char*));
                                if (relay_array) {
                                    int valid_relays = 0;
                                    for (int i = 0; i < relay_array_size; i++) {
                                        cJSON* relay_item = cJSON_GetArrayItem(user_relays, i);
                                        if (relay_item && cJSON_IsString(relay_item)) {
                                            const char* relay_url = cJSON_GetStringValue(relay_item);
                                            if (relay_url && strlen(relay_url) > 0) {
                                                relay_array[valid_relays] = malloc(strlen(relay_url) + 1);
                                                if (relay_array[valid_relays]) {
                                                    strcpy(relay_array[valid_relays], relay_url);
                                                    valid_relays++;
                                                }
                                            }
                                        }
                                    }
                                    
                                    if (valid_relays > 0) {
                                        *relays = relay_array;
                                        *relay_count = valid_relays;
                                    } else {
                                        free(relay_array);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    cJSON_Delete(json);
    return result;
}

/**
 * Lookup a public key from a NIP-05 identifier
 */
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
                       char*** relays, int* relay_count, int timeout_seconds) {
    if (!nip05_identifier) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    char local_part[64];
    char domain[256];
    char url[NOSTR_MAX_URL_SIZE];
    
    // Parse the identifier
    int parse_result = nip05_parse_identifier(nip05_identifier, local_part, domain);
    if (parse_result != NOSTR_SUCCESS) {
        return parse_result;
    }
    
    // Construct the .well-known URL
    int url_result = snprintf(url, sizeof(url), "https://%s/.well-known/nostr.json?name=%s", 
                             domain, local_part);
    if (url_result >= (int)sizeof(url) || url_result < 0) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Make HTTP request
    char* response_data = NULL;
    int http_result = nip05_http_get(url, timeout_seconds, &response_data);
    if (http_result != NOSTR_SUCCESS) {
        return http_result;
    }
    
    // Parse the response
    int parse_response_result = nostr_nip05_parse_well_known(response_data, local_part,
                                                            pubkey_hex_out, relays, relay_count);
    
    free(response_data);
    return parse_response_result;
}

/**
 * Verify a NIP-05 identifier against a public key
 */
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex, 
                       char*** relays, int* relay_count, int timeout_seconds) {
    if (!nip05_identifier || !pubkey_hex) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    // Validate the input public key format
    if (nip05_validate_pubkey_hex(pubkey_hex) != NOSTR_SUCCESS) {
        return NOSTR_ERROR_INVALID_INPUT;
    }
    
    char found_pubkey[65];
    
    // Lookup the public key for this identifier
    int lookup_result = nostr_nip05_lookup(nip05_identifier, found_pubkey, 
                                          relays, relay_count, timeout_seconds);
    if (lookup_result != NOSTR_SUCCESS) {
        return lookup_result;
    }
    
    // Compare the public keys (case insensitive)
    if (strcasecmp(pubkey_hex, found_pubkey) != 0) {
        // Clean up relays if verification failed
        if (relays && *relays) {
            for (int i = 0; i < (relay_count ? *relay_count : 0); i++) {
                free((*relays)[i]);
            }
            free(*relays);
            *relays = NULL;
        }
        if (relay_count) {
            *relay_count = 0;
        }
        return NOSTR_ERROR_NIP05_PUBKEY_MISMATCH;
    }
    
    return NOSTR_SUCCESS;
}