#define _POSIX_C_SOURCE 200809L

#include "tools_internal.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cjson/cJSON.h"
#include "../../nostr_core_lib/nostr_core/nostr_core.h"

static char* json_error_local(const char* msg) {
    cJSON* root = cJSON_CreateObject();
    if (!root) return NULL;
    cJSON_AddBoolToObject(root, "success", 0);
    cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
    char* out = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    return out;
}

static cJSON* parse_args_local(const char* args_json) {
    const char* raw = args_json ? args_json : "{}";
    cJSON* args = cJSON_Parse(raw);
    if (!args || !cJSON_IsObject(args)) {
        cJSON_Delete(args);
        return NULL;
    }
    return args;
}

static int is_hex_string_len_local(const char* s, size_t expected_len) {
    if (!s) return 0;
    if (strlen(s) != expected_len) return 0;
    for (size_t i = 0; i < expected_len; i++) {
        if (!((s[i] >= '0' && s[i] <= '9') ||
              (s[i] >= 'a' && s[i] <= 'f') ||
              (s[i] >= 'A' && s[i] <= 'F'))) {
            return 0;
        }
    }
    return 1;
}

char* execute_nostr_encode(const char* args_json) {
    cJSON* args = parse_args_local(args_json);
    if (!args) return json_error_local("invalid arguments JSON");

    cJSON* type = cJSON_GetObjectItemCaseSensitive(args, "type");
    cJSON* hex = cJSON_GetObjectItemCaseSensitive(args, "hex");
    cJSON* relays = cJSON_GetObjectItemCaseSensitive(args, "relays");
    cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
    cJSON* identifier = cJSON_GetObjectItemCaseSensitive(args, "identifier");

    if (!type || !cJSON_IsString(type) || !type->valuestring ||
        !hex || !cJSON_IsString(hex) || !hex->valuestring || !is_hex_string_len_local(hex->valuestring, 64U)) {
        cJSON_Delete(args);
        return json_error_local("nostr_encode requires type string and hex 64-char string");
    }

    char type_name[32];
    snprintf(type_name, sizeof(type_name), "%s", type->valuestring);

    unsigned char key_bytes[32];
    if (nostr_hex_to_bytes(hex->valuestring, key_bytes, sizeof(key_bytes)) != 0) {
        cJSON_Delete(args);
        return json_error_local("nostr_encode invalid hex");
    }

    const char** relay_ptrs = NULL;
    int relay_count = 0;
    if (relays) {
        if (!cJSON_IsArray(relays)) {
            cJSON_Delete(args);
            return json_error_local("nostr_encode relays must be an array of relay URLs");
        }
        relay_count = cJSON_GetArraySize(relays);
        if (relay_count > 0) {
            relay_ptrs = (const char**)malloc((size_t)relay_count * sizeof(char*));
            if (!relay_ptrs) {
                cJSON_Delete(args);
                return json_error_local("memory allocation failed");
            }
            for (int i = 0; i < relay_count; i++) {
                cJSON* relay = cJSON_GetArrayItem(relays, i);
                if (!relay || !cJSON_IsString(relay) || !relay->valuestring || relay->valuestring[0] == '\0') {
                    free(relay_ptrs);
                    cJSON_Delete(args);
                    return json_error_local("nostr_encode relays must contain non-empty strings");
                }
                relay_ptrs[i] = relay->valuestring;
            }
        }
    }

    char bech32[256] = {0};
    char uri[1200] = {0};
    int rc = NOSTR_ERROR_INVALID_INPUT;

    if (strcmp(type_name, "npub") == 0) {
        rc = nostr_key_to_bech32(key_bytes, "npub", bech32);
        if (rc == NOSTR_SUCCESS) {
            snprintf(uri, sizeof(uri), "nostr:%s", bech32);
        }
    } else if (strcmp(type_name, "nsec") == 0) {
        rc = nostr_key_to_bech32(key_bytes, "nsec", bech32);
        if (rc == NOSTR_SUCCESS) {
            snprintf(uri, sizeof(uri), "nostr:%s", bech32);
        }
    } else if (strcmp(type_name, "note") == 0) {
        rc = nostr_key_to_bech32(key_bytes, "note", bech32);
        if (rc == NOSTR_SUCCESS) {
            snprintf(uri, sizeof(uri), "nostr:%s", bech32);
        }
    } else if (strcmp(type_name, "nprofile") == 0) {
        rc = nostr_build_uri_nprofile(key_bytes, relay_ptrs, relay_count, uri, sizeof(uri));
    } else if (strcmp(type_name, "nevent") == 0) {
        int kind_value = -1;
        if (kind) {
            if (!cJSON_IsNumber(kind)) {
                free(relay_ptrs);
                cJSON_Delete(args);
                return json_error_local("nostr_encode nevent kind must be an integer");
            }
            kind_value = (int)kind->valuedouble;
        }
        rc = nostr_build_uri_nevent(key_bytes, relay_ptrs, relay_count, NULL, kind_value, 0, uri, sizeof(uri));
    } else if (strcmp(type_name, "naddr") == 0) {
        if (!kind || !cJSON_IsNumber(kind)) {
            free(relay_ptrs);
            cJSON_Delete(args);
            return json_error_local("nostr_encode naddr requires kind integer");
        }
        if (!identifier || !cJSON_IsString(identifier) || !identifier->valuestring || identifier->valuestring[0] == '\0') {
            free(relay_ptrs);
            cJSON_Delete(args);
            return json_error_local("nostr_encode naddr requires non-empty identifier string");
        }
        rc = nostr_build_uri_naddr(identifier->valuestring, key_bytes, (int)kind->valuedouble,
                                   relay_ptrs, relay_count, uri, sizeof(uri));
    } else {
        free(relay_ptrs);
        cJSON_Delete(args);
        return json_error_local("nostr_encode currently supports npub, nsec, note, nprofile, nevent, naddr");
    }

    free(relay_ptrs);
    cJSON_Delete(args);
    if (rc != NOSTR_SUCCESS) {
        return json_error_local("nostr_encode failed");
    }

    cJSON* out = cJSON_CreateObject();
    if (!out) return NULL;
    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "type", type_name);
    cJSON_AddStringToObject(out, "uri", uri);
    cJSON_AddBoolToObject(out, "limited_support", 1);
    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    return json;
}

char* execute_nostr_decode(const char* args_json) {
    cJSON* args = parse_args_local(args_json);
    if (!args) return json_error_local("invalid arguments JSON");

    cJSON* uri_in = cJSON_GetObjectItemCaseSensitive(args, "uri");
    if (!uri_in || !cJSON_IsString(uri_in) || !uri_in->valuestring || uri_in->valuestring[0] == '\0') {
        cJSON_Delete(args);
        return json_error_local("nostr_decode requires uri string");
    }

    const char* in = uri_in->valuestring;
    if (strncmp(in, "nostr:", 6) == 0) {
        in += 6;
    }

    unsigned char key[32];
    char key_hex[65] = {0};
    const char* type = NULL;
    int rc = NOSTR_ERROR_INVALID_INPUT;

    if (strncmp(in, "npub1", 5) == 0) {
        type = "npub";
        rc = nostr_decode_npub(in, key);
    } else if (strncmp(in, "nsec1", 5) == 0) {
        type = "nsec";
        rc = nostr_decode_nsec(in, key);
    } else {
        cJSON_Delete(args);
        return json_error_local("nostr_decode currently supports npub and nsec");
    }

    cJSON_Delete(args);
    if (rc != NOSTR_SUCCESS) {
        return json_error_local("nostr_decode failed");
    }

    nostr_bytes_to_hex(key, 32, key_hex);

    cJSON* out = cJSON_CreateObject();
    if (!out) return NULL;
    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "type", type);
    if (strcmp(type, "npub") == 0) {
        cJSON_AddStringToObject(out, "pubkey", key_hex);
    } else {
        cJSON_AddStringToObject(out, "private_key", key_hex);
    }
    cJSON_AddBoolToObject(out, "limited_support", 1);

    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    return json;
}

char* execute_nostr_pubkey(tools_context_t* ctx, const char* args_json) {
    if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");

    cJSON* args = parse_args_local(args_json);
    if (!args) return json_error_local("invalid arguments JSON");
    cJSON_Delete(args);

    cJSON* out = cJSON_CreateObject();
    if (!out) return NULL;

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "pubkey", ctx->cfg->keys.public_key_hex);

    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    return json;
}

char* execute_nostr_npub(tools_context_t* ctx, const char* args_json) {
    if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");

    cJSON* args = parse_args_local(args_json);
    if (!args) return json_error_local("invalid arguments JSON");
    cJSON_Delete(args);

    char npub[256] = {0};
    if (nostr_key_to_bech32(ctx->cfg->keys.public_key, "npub", npub) != NOSTR_SUCCESS) {
        return json_error_local("failed to encode npub");
    }

    cJSON* out = cJSON_CreateObject();
    if (!out) return NULL;

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "npub", npub);

    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    return json;
}
