#define _POSIX_C_SOURCE 200809L

#include "tools_internal.h"

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

#include "cjson/cJSON.h"
#include "../nostr_handler.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;
}

static int add_string_tag_local(cJSON* tags, const char* key, const char* value) {
    if (!tags || !cJSON_IsArray(tags) || !key || !value) return -1;
    cJSON* tuple = cJSON_CreateArray();
    if (!tuple) return -1;
    cJSON_AddItemToArray(tuple, cJSON_CreateString(key));
    cJSON_AddItemToArray(tuple, cJSON_CreateString(value));
    cJSON_AddItemToArray(tags, tuple);
    return 0;
}

static void free_string_array_heap_local(char** items, int count) {
    if (!items) return;
    for (int i = 0; i < count; i++) free(items[i]);
    free(items);
}

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

    cJSON* event_id = cJSON_GetObjectItemCaseSensitive(args, "event_id");
    cJSON* event_pubkey = cJSON_GetObjectItemCaseSensitive(args, "event_pubkey");
    cJSON* event_kind = cJSON_GetObjectItemCaseSensitive(args, "event_kind");
    cJSON* reaction = cJSON_GetObjectItemCaseSensitive(args, "reaction");

    if (!event_id || !cJSON_IsString(event_id) || !event_id->valuestring || !is_hex_string_len_local(event_id->valuestring, 64U) ||
        !event_pubkey || !cJSON_IsString(event_pubkey) || !event_pubkey->valuestring || !is_hex_string_len_local(event_pubkey->valuestring, 64U)) {
        cJSON_Delete(args);
        return json_error_local("nostr_react requires event_id and event_pubkey as 64-char hex strings");
    }
    if (event_kind && !cJSON_IsNumber(event_kind)) {
        cJSON_Delete(args);
        return json_error_local("nostr_react event_kind must be an integer when provided");
    }
    if (reaction && !cJSON_IsString(reaction)) {
        cJSON_Delete(args);
        return json_error_local("nostr_react reaction must be a string when provided");
    }

    cJSON* tags = cJSON_CreateArray();
    if (!tags) {
        cJSON_Delete(args);
        return json_error_local("failed to create tags");
    }

    if (add_string_tag_local(tags, "e", event_id->valuestring) != 0 ||
        add_string_tag_local(tags, "p", event_pubkey->valuestring) != 0) {
        cJSON_Delete(tags);
        cJSON_Delete(args);
        return json_error_local("failed to add required reaction tags");
    }

    if (event_kind) {
        char kind_buf[32];
        snprintf(kind_buf, sizeof(kind_buf), "%d", (int)event_kind->valuedouble);
        if (add_string_tag_local(tags, "k", kind_buf) != 0) {
            cJSON_Delete(tags);
            cJSON_Delete(args);
            return json_error_local("failed to add k tag");
        }
    }

    const char* reaction_content = (reaction && reaction->valuestring && reaction->valuestring[0] != '\0') ? reaction->valuestring : "+";

    nostr_publish_result_t publish_result;
    memset(&publish_result, 0, sizeof(publish_result));

    int rc = nostr_handler_publish_kind_event(7, reaction_content, tags, &publish_result);
    cJSON_Delete(tags);
    cJSON_Delete(args);
    if (rc != 0) {
        nostr_handler_publish_result_free(&publish_result);
        return json_error_local("nostr_react failed");
    }

    cJSON* out = cJSON_CreateObject();
    if (!out) {
        nostr_handler_publish_result_free(&publish_result);
        return NULL;
    }

    cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
    cJSON_AddStringToObject(out, "message", "nostr_react published");
    cJSON_AddStringToObject(out, "reaction", reaction_content);
    cJSON_AddNumberToObject(out, "kind", publish_result.kind);
    cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
    cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
    cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);

    cJSON* relays = cJSON_CreateArray();
    if (!relays) {
        nostr_handler_publish_result_free(&publish_result);
        cJSON_Delete(out);
        return NULL;
    }
    for (int i = 0; i < publish_result.relay_count; i++) {
        cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
    }
    cJSON_AddItemToObject(out, "relays", relays);

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

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

    cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
    if (!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len_local(pubkey->valuestring, 64U)) {
        cJSON_Delete(args);
        return json_error_local("nostr_profile_get requires pubkey as 64-char hex string");
    }

    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON* authors = cJSON_CreateArray();
    if (!filter || !kinds || !authors) {
        cJSON_Delete(filter);
        cJSON_Delete(kinds);
        cJSON_Delete(authors);
        cJSON_Delete(args);
        return json_error_local("failed to create profile query filter");
    }

    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(0));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey->valuestring));
    cJSON_AddItemToObject(filter, "authors", authors);
    cJSON_AddNumberToObject(filter, "limit", 1);

    char* events_json = nostr_handler_query_json(filter, 8000);
    cJSON_Delete(filter);
    cJSON_Delete(args);
    if (!events_json) {
        return json_error_local("nostr_profile_get query failed");
    }

    cJSON* events = cJSON_Parse(events_json);
    free(events_json);
    if (!events || !cJSON_IsArray(events)) {
        cJSON_Delete(events);
        return json_error_local("nostr_profile_get returned invalid events JSON");
    }

    cJSON* out = cJSON_CreateObject();
    if (!out) {
        cJSON_Delete(events);
        return NULL;
    }

    cJSON_AddBoolToObject(out, "success", 1);

    int found = cJSON_GetArraySize(events) > 0;
    cJSON_AddBoolToObject(out, "found", found ? 1 : 0);

    if (found) {
        cJSON* ev = cJSON_GetArrayItem(events, 0);
        cJSON* ev_dup = ev ? cJSON_Duplicate(ev, 1) : NULL;
        if (ev_dup) {
            cJSON_AddItemToObject(out, "event", ev_dup);
        }

        cJSON* content = ev ? cJSON_GetObjectItemCaseSensitive(ev, "content") : NULL;
        if (content && cJSON_IsString(content) && content->valuestring) {
            cJSON* profile = cJSON_Parse(content->valuestring);
            if (profile && cJSON_IsObject(profile)) {
                cJSON_AddItemToObject(out, "profile", profile);
            } else {
                cJSON_Delete(profile);
                cJSON_AddStringToObject(out, "profile_raw", content->valuestring);
            }
        }
    }

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

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

    cJSON* identifier = cJSON_GetObjectItemCaseSensitive(args, "identifier");
    cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");

    if (!identifier || !cJSON_IsString(identifier) || !identifier->valuestring || strchr(identifier->valuestring, '@') == NULL) {
        cJSON_Delete(args);
        return json_error_local("nostr_nip05_lookup requires identifier in user@domain format");
    }
    if (pubkey && (!cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len_local(pubkey->valuestring, 64U))) {
        cJSON_Delete(args);
        return json_error_local("nostr_nip05_lookup pubkey must be a 64-char hex string when provided");
    }

    char found_pubkey[65] = {0};
    char** relays = NULL;
    int relay_count = 0;
    int rc = 0;

    if (pubkey) {
        rc = nostr_nip05_verify(identifier->valuestring,
                                pubkey->valuestring,
                                &relays,
                                &relay_count,
                                10);
    } else {
        rc = nostr_nip05_lookup(identifier->valuestring,
                                found_pubkey,
                                &relays,
                                &relay_count,
                                10);
    }

    cJSON* out = cJSON_CreateObject();
    if (!out) {
        free_string_array_heap_local(relays, relay_count);
        cJSON_Delete(args);
        return NULL;
    }

    cJSON_AddBoolToObject(out, "success", rc == NOSTR_SUCCESS ? 1 : 0);
    cJSON_AddStringToObject(out, "identifier", identifier->valuestring);

    if (pubkey) {
        cJSON_AddBoolToObject(out, "verified", rc == NOSTR_SUCCESS ? 1 : 0);
        cJSON_AddStringToObject(out, "pubkey", pubkey->valuestring);
    } else if (rc == NOSTR_SUCCESS) {
        cJSON_AddStringToObject(out, "pubkey", found_pubkey);
    }

    cJSON* relay_arr = cJSON_CreateArray();
    if (!relay_arr) {
        cJSON_Delete(out);
        free_string_array_heap_local(relays, relay_count);
        cJSON_Delete(args);
        return NULL;
    }
    for (int i = 0; i < relay_count; i++) {
        cJSON_AddItemToArray(relay_arr, cJSON_CreateString(relays[i] ? relays[i] : ""));
    }
    cJSON_AddItemToObject(out, "relays", relay_arr);

    if (rc != NOSTR_SUCCESS) {
        cJSON_AddNumberToObject(out, "error_code", rc);
    }

    free_string_array_heap_local(relays, relay_count);
    cJSON_Delete(args);
    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    return json;
}
