#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"

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 int tag_tuple_equal_local(cJSON* a, cJSON* b) {
    if (!a || !b || !cJSON_IsArray(a) || !cJSON_IsArray(b)) return 0;
    int an = cJSON_GetArraySize(a);
    int bn = cJSON_GetArraySize(b);
    if (an != bn) return 0;
    for (int i = 0; i < an; i++) {
        cJSON* av = cJSON_GetArrayItem(a, i);
        cJSON* bv = cJSON_GetArrayItem(b, i);
        if (!av || !bv || !cJSON_IsString(av) || !cJSON_IsString(bv) || !av->valuestring || !bv->valuestring) {
            return 0;
        }
        if (strcmp(av->valuestring, bv->valuestring) != 0) return 0;
    }
    return 1;
}

static int tags_contains_tuple_local(cJSON* tags, cJSON* tuple) {
    if (!tags || !tuple || !cJSON_IsArray(tags) || !cJSON_IsArray(tuple)) return 0;
    int n = cJSON_GetArraySize(tags);
    for (int i = 0; i < n; i++) {
        cJSON* t = cJSON_GetArrayItem(tags, i);
        if (tag_tuple_equal_local(t, tuple)) return 1;
    }
    return 0;
}

static int remove_matching_tag_tuples_local(cJSON* tags, cJSON* tuple) {
    if (!tags || !tuple || !cJSON_IsArray(tags) || !cJSON_IsArray(tuple)) return 0;
    int removed = 0;
    for (int i = cJSON_GetArraySize(tags) - 1; i >= 0; i--) {
        cJSON* t = cJSON_GetArrayItem(tags, i);
        if (tag_tuple_equal_local(t, tuple)) {
            cJSON_DeleteItemFromArray(tags, i);
            removed++;
        }
    }
    return removed;
}

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

    cJSON* event_ids = cJSON_GetObjectItemCaseSensitive(args, "event_ids");
    cJSON* kinds = cJSON_GetObjectItemCaseSensitive(args, "kinds");
    cJSON* reason = cJSON_GetObjectItemCaseSensitive(args, "reason");

    if (!event_ids || !cJSON_IsArray(event_ids) || cJSON_GetArraySize(event_ids) <= 0) {
        cJSON_Delete(args);
        return json_error_local("nostr_delete requires non-empty event_ids array");
    }
    if (kinds && !cJSON_IsArray(kinds)) {
        cJSON_Delete(args);
        return json_error_local("nostr_delete kinds must be an array when provided");
    }
    if (reason && !cJSON_IsString(reason)) {
        cJSON_Delete(args);
        return json_error_local("nostr_delete reason must be a string when provided");
    }

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

    int event_id_count = cJSON_GetArraySize(event_ids);
    for (int i = 0; i < event_id_count; i++) {
        cJSON* id = cJSON_GetArrayItem(event_ids, i);
        if (!id || !cJSON_IsString(id) || !id->valuestring || !is_hex_string_len_local(id->valuestring, 64U)) {
            cJSON_Delete(tags);
            cJSON_Delete(args);
            return json_error_local("nostr_delete event_ids must contain 64-char hex strings");
        }
        if (add_string_tag_local(tags, "e", id->valuestring) != 0) {
            cJSON_Delete(tags);
            cJSON_Delete(args);
            return json_error_local("failed to add e tag");
        }
    }

    if (kinds) {
        int kind_count = cJSON_GetArraySize(kinds);
        for (int i = 0; i < kind_count; i++) {
            cJSON* k = cJSON_GetArrayItem(kinds, i);
            if (!k || !cJSON_IsNumber(k)) {
                cJSON_Delete(tags);
                cJSON_Delete(args);
                return json_error_local("nostr_delete kinds must contain integers");
            }
            char kind_buf[32];
            snprintf(kind_buf, sizeof(kind_buf), "%d", (int)k->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");
            }
        }
    }

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

    const char* reason_content = (reason && reason->valuestring) ? reason->valuestring : "";
    int rc = nostr_handler_publish_kind_event(5, reason_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_delete 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_delete published");
    cJSON_AddNumberToObject(out, "kind", publish_result.kind);
    cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
    cJSON_AddNumberToObject(out, "requested_event_count", event_id_count);
    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_list_manage(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* list_kind = cJSON_GetObjectItemCaseSensitive(args, "list_kind");
    cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
    cJSON* items = cJSON_GetObjectItemCaseSensitive(args, "items");

    if (!list_kind || !cJSON_IsNumber(list_kind) ||
        !action || !cJSON_IsString(action) || !action->valuestring ||
        !items || !cJSON_IsArray(items)) {
        cJSON_Delete(args);
        return json_error_local("nostr_list_manage requires list_kind, action, and items");
    }
    if (strcmp(action->valuestring, "add") != 0 && strcmp(action->valuestring, "remove") != 0) {
        cJSON_Delete(args);
        return json_error_local("nostr_list_manage action must be add or remove");
    }

    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 list query filter");
    }

    cJSON_AddItemToArray(kinds, cJSON_CreateNumber((int)list_kind->valuedouble));
    cJSON_AddItemToObject(filter, "kinds", kinds);
    cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
    cJSON_AddItemToObject(filter, "authors", authors);
    cJSON_AddNumberToObject(filter, "limit", 1);

    char* events_json = nostr_handler_query_json(filter, 8000);
    cJSON_Delete(filter);

    cJSON* events = events_json ? cJSON_Parse(events_json) : NULL;
    free(events_json);

    char* list_content = strdup("");
    if (!list_content) {
        cJSON_Delete(events);
        cJSON_Delete(args);
        return json_error_local("allocation failure");
    }

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

    if (events && cJSON_IsArray(events) && cJSON_GetArraySize(events) > 0) {
        cJSON* ev0 = cJSON_GetArrayItem(events, 0);
        if (ev0 && cJSON_IsObject(ev0)) {
            cJSON* content = cJSON_GetObjectItemCaseSensitive(ev0, "content");
            if (content && cJSON_IsString(content) && content->valuestring) {
                free(list_content);
                list_content = strdup(content->valuestring);
                if (!list_content) {
                    cJSON_Delete(updated_tags);
                    cJSON_Delete(events);
                    cJSON_Delete(args);
                    return json_error_local("allocation failure");
                }
            }

            cJSON* tags = cJSON_GetObjectItemCaseSensitive(ev0, "tags");
            if (tags && cJSON_IsArray(tags)) {
                cJSON_Delete(updated_tags);
                updated_tags = cJSON_Duplicate(tags, 1);
                if (!updated_tags) {
                    free(list_content);
                    cJSON_Delete(events);
                    cJSON_Delete(args);
                    return json_error_local("failed to duplicate existing list tags");
                }
            }
        }
    }

    int items_affected = 0;
    int item_count = cJSON_GetArraySize(items);
    for (int i = 0; i < item_count; i++) {
        cJSON* tuple = cJSON_GetArrayItem(items, i);
        if (!tuple || !cJSON_IsArray(tuple) || cJSON_GetArraySize(tuple) <= 0) {
            continue;
        }

        int tuple_ok = 1;
        int tuple_size = cJSON_GetArraySize(tuple);
        for (int j = 0; j < tuple_size; j++) {
            cJSON* part = cJSON_GetArrayItem(tuple, j);
            if (!part || !cJSON_IsString(part) || !part->valuestring) {
                tuple_ok = 0;
                break;
            }
        }
        if (!tuple_ok) {
            continue;
        }

        if (strcmp(action->valuestring, "add") == 0) {
            if (!tags_contains_tuple_local(updated_tags, tuple)) {
                cJSON* dup = cJSON_Duplicate(tuple, 1);
                if (!dup) {
                    free(list_content);
                    cJSON_Delete(updated_tags);
                    cJSON_Delete(events);
                    cJSON_Delete(args);
                    return json_error_local("failed to duplicate list item");
                }
                cJSON_AddItemToArray(updated_tags, dup);
                items_affected++;
            }
        } else {
            items_affected += remove_matching_tag_tuples_local(updated_tags, tuple);
        }
    }

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

    int rc = nostr_handler_publish_kind_event((int)list_kind->valuedouble,
                                              list_content,
                                              updated_tags,
                                              &publish_result);

    free(list_content);
    cJSON_Delete(updated_tags);
    cJSON_Delete(events);

    if (rc != 0) {
        cJSON_Delete(args);
        nostr_handler_publish_result_free(&publish_result);
        return json_error_local("nostr_list_manage failed");
    }

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

    cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
    cJSON_AddNumberToObject(out, "list_kind", publish_result.kind);
    cJSON_AddStringToObject(out, "action", action->valuestring);
    cJSON_Delete(args);
    cJSON_AddNumberToObject(out, "items_affected", items_affected);
    cJSON_AddStringToObject(out, "event_id", publish_result.event_id);

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