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

#define CONFIG_KIND 30078
#define CONFIG_APP_TAG "didactyl"

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 nip44_encrypt_self_local(tools_context_t* ctx, const char* plaintext, char** out_ciphertext) {
    if (!ctx || !ctx->cfg || !out_ciphertext) return -1;

    const char* plain = plaintext ? plaintext : "";
    size_t out_cap = (strlen(plain) * 4U) + 1024U;
    char* ciphertext = (char*)malloc(out_cap);
    if (!ciphertext) return -1;

    int rc = nostr_nip44_encrypt(ctx->cfg->keys.private_key,
                                 ctx->cfg->keys.public_key,
                                 plain,
                                 ciphertext,
                                 out_cap);
    if (rc != NOSTR_SUCCESS) {
        free(ciphertext);
        return -1;
    }

    *out_ciphertext = ciphertext;
    return 0;
}

static int nip44_decrypt_self_local(tools_context_t* ctx, const char* ciphertext, char** out_plaintext) {
    if (!ctx || !ctx->cfg || !ciphertext || !out_plaintext) return -1;

    size_t out_cap = strlen(ciphertext) + 1024U;
    char* plaintext = (char*)malloc(out_cap);
    if (!plaintext) return -1;

    int rc = nostr_nip44_decrypt(ctx->cfg->keys.private_key,
                                 ctx->cfg->keys.public_key,
                                 ciphertext,
                                 plaintext,
                                 out_cap);
    if (rc != NOSTR_SUCCESS) {
        free(plaintext);
        return -1;
    }

    *out_plaintext = plaintext;
    return 0;
}

static int query_config_ciphertext_local(tools_context_t* ctx, const char* d_tag, char** out_ciphertext) {
    if (!ctx || !ctx->cfg || !d_tag || !d_tag[0] || !out_ciphertext) return -1;

    cJSON* filter = cJSON_CreateObject();
    cJSON* kinds = cJSON_CreateArray();
    cJSON* authors = cJSON_CreateArray();
    cJSON* d_vals = cJSON_CreateArray();
    if (!filter || !kinds || !authors || !d_vals) {
        cJSON_Delete(filter);
        cJSON_Delete(kinds);
        cJSON_Delete(authors);
        cJSON_Delete(d_vals);
        return -1;
    }

    cJSON_AddItemToArray(kinds, cJSON_CreateNumber(CONFIG_KIND));
    cJSON_AddItemToObject(filter, "kinds", kinds);

    cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
    cJSON_AddItemToObject(filter, "authors", authors);

    cJSON_AddItemToArray(d_vals, cJSON_CreateString(d_tag));
    cJSON_AddItemToObject(filter, "#d", d_vals);

    cJSON_AddNumberToObject(filter, "limit", 1);

    char* events_json = nostr_handler_query_json(filter, 4000);
    cJSON_Delete(filter);
    if (!events_json) {
        return -1;
    }

    cJSON* arr = cJSON_Parse(events_json);
    free(events_json);
    if (!arr || !cJSON_IsArray(arr) || cJSON_GetArraySize(arr) <= 0) {
        cJSON_Delete(arr);
        *out_ciphertext = strdup("");
        return (*out_ciphertext != NULL) ? 0 : -1;
    }

    cJSON* ev = cJSON_GetArrayItem(arr, 0);
    cJSON* content = ev ? cJSON_GetObjectItemCaseSensitive(ev, "content") : NULL;
    const char* cipher = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : "";

    *out_ciphertext = strdup(cipher);
    cJSON_Delete(arr);
    return (*out_ciphertext != NULL) ? 0 : -1;
}

char* execute_config_store(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* d_tag_j = cJSON_GetObjectItemCaseSensitive(args, "d_tag");
    cJSON* content_j = cJSON_GetObjectItemCaseSensitive(args, "content");

    if (!d_tag_j || !cJSON_IsString(d_tag_j) || !d_tag_j->valuestring || d_tag_j->valuestring[0] == '\0') {
        cJSON_Delete(args);
        return json_error_local("config_store requires non-empty string d_tag");
    }
    if (!content_j) {
        cJSON_Delete(args);
        return json_error_local("config_store requires content");
    }

    char d_tag_buf[128];
    snprintf(d_tag_buf, sizeof(d_tag_buf), "%s", d_tag_j->valuestring);

    char* content_text = NULL;
    if (cJSON_IsString(content_j) && content_j->valuestring) {
        content_text = strdup(content_j->valuestring);
    } else {
        content_text = cJSON_PrintUnformatted(content_j);
    }
    cJSON_Delete(args);

    if (!content_text) {
        return json_error_local("config_store failed to serialize content");
    }

    char* ciphertext = NULL;
    if (nip44_encrypt_self_local(ctx, content_text, &ciphertext) != 0) {
        free(content_text);
        return json_error_local("config_store NIP-44 encryption failed");
    }

    cJSON* tags = cJSON_CreateArray();
    if (!tags) {
        free(content_text);
        free(ciphertext);
        return NULL;
    }

    cJSON* d_tag = cJSON_CreateArray();
    cJSON* app_tag = cJSON_CreateArray();
    if (!d_tag || !app_tag) {
        cJSON_Delete(d_tag);
        cJSON_Delete(app_tag);
        cJSON_Delete(tags);
        free(content_text);
        free(ciphertext);
        return NULL;
    }

    cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
    cJSON_AddItemToArray(d_tag, cJSON_CreateString(d_tag_buf));
    cJSON_AddItemToArray(tags, d_tag);

    cJSON_AddItemToArray(app_tag, cJSON_CreateString("app"));
    cJSON_AddItemToArray(app_tag, cJSON_CreateString(CONFIG_APP_TAG));
    cJSON_AddItemToArray(tags, app_tag);

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

    int rc = nostr_handler_publish_kind_event(CONFIG_KIND, ciphertext, tags, &publish_result);
    cJSON_Delete(tags);
    free(ciphertext);

    if (rc != 0) {
        free(content_text);
        nostr_handler_publish_result_free(&publish_result);
        return json_error_local("config_store publish failed");
    }

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

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "d_tag", d_tag_buf);
    cJSON_AddNumberToObject(out, "kind", CONFIG_KIND);
    cJSON_AddNumberToObject(out, "content_length", (double)strlen(content_text));
    cJSON_AddStringToObject(out, "event_id", publish_result.event_id);

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

char* execute_config_recall(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* d_tag_j = cJSON_GetObjectItemCaseSensitive(args, "d_tag");
    if (!d_tag_j || !cJSON_IsString(d_tag_j) || !d_tag_j->valuestring || d_tag_j->valuestring[0] == '\0') {
        cJSON_Delete(args);
        return json_error_local("config_recall requires non-empty string d_tag");
    }

    char d_tag_buf[256];
    snprintf(d_tag_buf, sizeof(d_tag_buf), "%s", d_tag_j->valuestring);
    cJSON_Delete(args);

    char* ciphertext = NULL;
    if (query_config_ciphertext_local(ctx, d_tag_buf, &ciphertext) != 0) {
        return json_error_local("config_recall query failed");
    }

    if (!ciphertext || ciphertext[0] == '\0') {
        free(ciphertext);
        cJSON* out = cJSON_CreateObject();
        if (!out) return NULL;
        cJSON_AddBoolToObject(out, "success", 1);
        cJSON_AddBoolToObject(out, "found", 0);
        cJSON_AddStringToObject(out, "d_tag", d_tag_buf);
        cJSON_AddStringToObject(out, "content", "");
        char* json = cJSON_PrintUnformatted(out);
        cJSON_Delete(out);
        return json;
    }

    char* plaintext = NULL;
    if (nip44_decrypt_self_local(ctx, ciphertext, &plaintext) != 0) {
        free(ciphertext);
        return json_error_local("config_recall NIP-44 decrypt failed");
    }
    free(ciphertext);

    cJSON* out = cJSON_CreateObject();
    if (!out) {
        free(plaintext);
        return NULL;
    }

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddBoolToObject(out, "found", 1);
    cJSON_AddStringToObject(out, "d_tag", d_tag_buf);
    cJSON_AddStringToObject(out, "content", plaintext);
    cJSON_AddNumberToObject(out, "content_length", (double)strlen(plaintext));

    cJSON* parsed = cJSON_Parse(plaintext);
    if (parsed) {
        cJSON_AddItemToObject(out, "content_json", parsed);
    }

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