#define _POSIX_C_SOURCE 200809L

#include "tools_internal.h"

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

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

#define TASKS_KIND 30078
#define TASKS_D_TAG "tasks"
#define TASKS_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 cJSON* tasks_create_empty_root_local(void) {
    cJSON* root = cJSON_CreateObject();
    cJSON* tasks = cJSON_CreateArray();
    if (!root || !tasks) {
        cJSON_Delete(root);
        cJSON_Delete(tasks);
        return NULL;
    }
    cJSON_AddItemToObject(root, "tasks", tasks);
    cJSON_AddNumberToObject(root, "next_id", 1);
    return root;
}

static const char* normalize_task_status_local(const char* status) {
    if (!status) return NULL;
    if (strcmp(status, "pending") == 0) return "pending";
    if (strcmp(status, "active") == 0) return "active";
    if (strcmp(status, "done") == 0) return "done";
    return NULL;
}

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_tasks_ciphertext_local(tools_context_t* ctx, char** out_ciphertext) {
    if (!ctx || !ctx->cfg || !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(TASKS_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(TASKS_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;
}

static cJSON* tasks_load_root_nostr_local(tools_context_t* ctx) {
    char* ciphertext = NULL;
    if (query_tasks_ciphertext_local(ctx, &ciphertext) != 0) {
        return NULL;
    }

    if (!ciphertext || ciphertext[0] == '\0') {
        free(ciphertext);
        return tasks_create_empty_root_local();
    }

    char* plaintext = NULL;
    if (nip44_decrypt_self_local(ctx, ciphertext, &plaintext) != 0) {
        free(ciphertext);
        return NULL;
    }
    free(ciphertext);

    cJSON* root = cJSON_Parse(plaintext);
    free(plaintext);
    if (!root || !cJSON_IsObject(root)) {
        cJSON_Delete(root);
        return NULL;
    }

    cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks");
    if (!tasks || !cJSON_IsArray(tasks)) {
        cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks");
        cJSON_AddItemToObject(root, "tasks", cJSON_CreateArray());
    }

    cJSON* next_id = cJSON_GetObjectItemCaseSensitive(root, "next_id");
    if (!next_id || !cJSON_IsNumber(next_id) || next_id->valuedouble < 1) {
        cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id");
        cJSON_AddNumberToObject(root, "next_id", 1);
    }

    return root;
}

static int tasks_save_root_nostr_local(tools_context_t* ctx, cJSON* root) {
    if (!ctx || !ctx->cfg || !root) return -1;

    char* plaintext = cJSON_PrintUnformatted(root);
    if (!plaintext) return -1;

    char* ciphertext = NULL;
    if (nip44_encrypt_self_local(ctx, plaintext, &ciphertext) != 0) {
        free(plaintext);
        return -1;
    }
    free(plaintext);

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

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

    cJSON_AddItemToArray(app_tag, cJSON_CreateString("app"));
    cJSON_AddItemToArray(app_tag, cJSON_CreateString(TASKS_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(TASKS_KIND, ciphertext, tags, &publish_result);
    cJSON_Delete(tags);
    free(ciphertext);
    nostr_handler_publish_result_free(&publish_result);
    return rc == 0 ? 0 : -1;
}

static cJSON* task_find_by_id_local(cJSON* tasks, int id, int* out_index) {
    if (!tasks || !cJSON_IsArray(tasks) || id <= 0) return NULL;
    int n = cJSON_GetArraySize(tasks);
    for (int i = 0; i < n; i++) {
        cJSON* task = cJSON_GetArrayItem(tasks, i);
        cJSON* tid = task ? cJSON_GetObjectItemCaseSensitive(task, "id") : NULL;
        if (tid && cJSON_IsNumber(tid) && (int)tid->valuedouble == id) {
            if (out_index) *out_index = i;
            return task;
        }
    }
    return NULL;
}

char* execute_task_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* action = cJSON_GetObjectItemCaseSensitive(args, "action");
    if (!action || !cJSON_IsString(action) || !action->valuestring || action->valuestring[0] == '\0') {
        cJSON_Delete(args);
        return json_error_local("task_manage requires string action");
    }

    cJSON* root = tasks_load_root_nostr_local(ctx);
    if (!root) {
        cJSON_Delete(args);
        return json_error_local("failed to load tasks memory");
    }

    cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks");
    if (!tasks || !cJSON_IsArray(tasks)) {
        cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks");
        tasks = cJSON_CreateArray();
        cJSON_AddItemToObject(root, "tasks", tasks);
    }

    cJSON* next_id_item = cJSON_GetObjectItemCaseSensitive(root, "next_id");
    int next_id = (next_id_item && cJSON_IsNumber(next_id_item) && next_id_item->valuedouble >= 1)
                    ? (int)next_id_item->valuedouble
                    : 1;

    const char* action_s = action->valuestring;
    int mutated = 0;

    if (strcmp(action_s, "list") == 0) {
    } else if (strcmp(action_s, "add") == 0) {
        cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text");
        cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");
        if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task_manage add requires non-empty string text");
        }

        const char* normalized_status = "pending";
        if (status && !cJSON_IsNull(status)) {
            if (!cJSON_IsString(status) || !status->valuestring) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage add status must be string when provided");
            }
            normalized_status = normalize_task_status_local(status->valuestring);
            if (!normalized_status) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage add status must be pending, active, or done");
            }
        }

        cJSON* task = cJSON_CreateObject();
        if (!task) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("allocation failure");
        }

        time_t now = time(NULL);
        cJSON_AddNumberToObject(task, "id", next_id++);
        cJSON_AddStringToObject(task, "text", text->valuestring);
        cJSON_AddStringToObject(task, "status", normalized_status);
        cJSON_AddNumberToObject(task, "created_at", (double)now);
        cJSON_AddNumberToObject(task, "updated_at", (double)now);
        cJSON_AddItemToArray(tasks, task);
        mutated = 1;
    } else if (strcmp(action_s, "update") == 0) {
        cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id");
        cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text");
        cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");

        if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task_manage update requires integer id");
        }

        if ((!text || cJSON_IsNull(text)) && (!status || cJSON_IsNull(status))) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task_manage update requires text and/or status");
        }

        cJSON* task = task_find_by_id_local(tasks, (int)id->valuedouble, NULL);
        if (!task) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task not found");
        }

        if (text && !cJSON_IsNull(text)) {
            if (!cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage update text must be non-empty string when provided");
            }
            cJSON_DeleteItemFromObjectCaseSensitive(task, "text");
            cJSON_AddStringToObject(task, "text", text->valuestring);
            mutated = 1;
        }

        if (status && !cJSON_IsNull(status)) {
            if (!cJSON_IsString(status) || !status->valuestring) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage update status must be string when provided");
            }
            const char* normalized_status = normalize_task_status_local(status->valuestring);
            if (!normalized_status) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage update status must be pending, active, or done");
            }
            cJSON_DeleteItemFromObjectCaseSensitive(task, "status");
            cJSON_AddStringToObject(task, "status", normalized_status);
            mutated = 1;
        }

        if (mutated) {
            time_t now = time(NULL);
            cJSON_DeleteItemFromObjectCaseSensitive(task, "updated_at");
            cJSON_AddNumberToObject(task, "updated_at", (double)now);
        }
    } else if (strcmp(action_s, "remove") == 0) {
        cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id");
        if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task_manage remove requires integer id");
        }

        int idx = -1;
        cJSON* task = task_find_by_id_local(tasks, (int)id->valuedouble, &idx);
        if (!task || idx < 0) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task not found");
        }

        cJSON_DeleteItemFromArray(tasks, idx);
        mutated = 1;
    } else if (strcmp(action_s, "clear") == 0) {
        cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");
        if (!status || cJSON_IsNull(status)) {
            while (cJSON_GetArraySize(tasks) > 0) {
                cJSON_DeleteItemFromArray(tasks, 0);
            }
            mutated = 1;
        } else {
            if (!cJSON_IsString(status) || !status->valuestring) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage clear status must be string when provided");
            }
            const char* normalized_status = normalize_task_status_local(status->valuestring);
            if (!normalized_status) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("task_manage clear status must be pending, active, or done");
            }

            int i = 0;
            while (i < cJSON_GetArraySize(tasks)) {
                cJSON* task = cJSON_GetArrayItem(tasks, i);
                cJSON* task_status = task ? cJSON_GetObjectItemCaseSensitive(task, "status") : NULL;
                const char* task_status_s = (task_status && cJSON_IsString(task_status) && task_status->valuestring)
                                              ? task_status->valuestring
                                              : "pending";
                if (strcmp(task_status_s, normalized_status) == 0) {
                    cJSON_DeleteItemFromArray(tasks, i);
                    mutated = 1;
                } else {
                    i++;
                }
            }
        }
    } else if (strcmp(action_s, "replace") == 0) {
        cJSON* tasks_in = cJSON_GetObjectItemCaseSensitive(args, "tasks");
        if (!tasks_in || !cJSON_IsArray(tasks_in)) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("task_manage replace requires array tasks");
        }

        while (cJSON_GetArraySize(tasks) > 0) {
            cJSON_DeleteItemFromArray(tasks, 0);
        }

        int n = cJSON_GetArraySize(tasks_in);
        time_t now = time(NULL);
        for (int i = 0; i < n; i++) {
            cJSON* text = cJSON_GetArrayItem(tasks_in, i);
            if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
                continue;
            }

            cJSON* task = cJSON_CreateObject();
            if (!task) {
                cJSON_Delete(args);
                cJSON_Delete(root);
                return json_error_local("allocation failure");
            }

            cJSON_AddNumberToObject(task, "id", next_id++);
            cJSON_AddStringToObject(task, "text", text->valuestring);
            cJSON_AddStringToObject(task, "status", "pending");
            cJSON_AddNumberToObject(task, "created_at", (double)now);
            cJSON_AddNumberToObject(task, "updated_at", (double)now);
            cJSON_AddItemToArray(tasks, task);
        }
        mutated = 1;
    } else {
        cJSON_Delete(args);
        cJSON_Delete(root);
        return json_error_local("task_manage action must be one of: list, add, update, remove, clear, replace");
    }

    if (mutated) {
        cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id");
        cJSON_AddNumberToObject(root, "next_id", next_id);
        if (tasks_save_root_nostr_local(ctx, root) != 0) {
            cJSON_Delete(args);
            cJSON_Delete(root);
            return json_error_local("failed to persist tasks memory");
        }
    }

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

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "action", action_s);
    cJSON_AddBoolToObject(out, "mutated", mutated ? 1 : 0);
    cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(tasks));

    cJSON* tasks_dup = cJSON_Duplicate(tasks, 1);
    if (!tasks_dup) {
        cJSON_Delete(args);
        cJSON_Delete(root);
        cJSON_Delete(out);
        return NULL;
    }
    cJSON_AddItemToObject(out, "tasks", tasks_dup);

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

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

    char* listed = execute_task_manage(ctx, "{\"action\":\"list\"}");
    if (!listed) return json_error_local("task_list failed to list tasks");

    cJSON* listed_root = cJSON_Parse(listed);
    if (!listed_root || !cJSON_IsObject(listed_root)) {
        cJSON_Delete(listed_root);
        free(listed);
        return json_error_local("task_list received invalid task_manage response");
    }

    cJSON* tasks = cJSON_GetObjectItemCaseSensitive(listed_root, "tasks");
    int tcount = (tasks && cJSON_IsArray(tasks)) ? cJSON_GetArraySize(tasks) : 0;

    size_t cap = 512U;
    char* content = (char*)malloc(cap);
    if (!content) {
        cJSON_Delete(listed_root);
        free(listed);
        return NULL;
    }
    size_t used = 0U;
    int n = snprintf(content + used,
                     cap - used,
                     "## Current Task List\n\n### Current Tasks\n\nYour active task list - short-term working memory for tracking plan steps.\n\n");
    if (n < 0) {
        free(content);
        cJSON_Delete(listed_root);
        free(listed);
        return NULL;
    }
    used += (size_t)n;

    int written = 0;
    for (int i = 0; i < tcount; i++) {
        cJSON* task = cJSON_GetArrayItem(tasks, i);
        cJSON* id = task ? cJSON_GetObjectItemCaseSensitive(task, "id") : NULL;
        cJSON* text = task ? cJSON_GetObjectItemCaseSensitive(task, "text") : NULL;
        cJSON* status = task ? cJSON_GetObjectItemCaseSensitive(task, "status") : NULL;
        if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
            continue;
        }

        const char* status_s = (status && cJSON_IsString(status) && status->valuestring) ? status->valuestring : "pending";
        const char* mark = " ";
        if (strcmp(status_s, "done") == 0) mark = "x";
        else if (strcmp(status_s, "active") == 0) mark = "-";

        int id_value = (id && cJSON_IsNumber(id)) ? (int)id->valuedouble : (i + 1);

        int needed = snprintf(NULL, 0, "- [%s] %d. %s\n", mark, id_value, text->valuestring);
        if (needed < 0) continue;
        if (used + (size_t)needed + 1U > cap) {
            size_t next = cap;
            while (used + (size_t)needed + 1U > next) next *= 2U;
            char* grown = (char*)realloc(content, next);
            if (!grown) {
                free(content);
                cJSON_Delete(listed_root);
                free(listed);
                return NULL;
            }
            content = grown;
            cap = next;
        }
        snprintf(content + used, cap - used, "- [%s] %d. %s\n", mark, id_value, text->valuestring);
        used += (size_t)needed;
        written++;
    }

    if (written == 0) {
        const char* fallback = "- [ ] No active tasks yet.\n";
        size_t need = strlen(fallback);
        if (used + need + 1U > cap) {
            size_t next = cap;
            while (used + need + 1U > next) next *= 2U;
            char* grown = (char*)realloc(content, next);
            if (!grown) {
                free(content);
                cJSON_Delete(listed_root);
                free(listed);
                return NULL;
            }
            content = grown;
            cap = next;
        }
        memcpy(content + used, fallback, need + 1U);
        used += need;
    }

    cJSON* args = parse_args_local(args_json);
    if (!args) {
        free(content);
        cJSON_Delete(listed_root);
        free(listed);
        return json_error_local("invalid arguments JSON");
    }
    cJSON_Delete(args);

    cJSON* out = cJSON_CreateObject();
    if (!out) {
        free(content);
        cJSON_Delete(listed_root);
        free(listed);
        return NULL;
    }

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "content", content);
    cJSON* tasks_dup = tasks ? cJSON_Duplicate(tasks, 1) : cJSON_CreateArray();
    if (!tasks_dup) tasks_dup = cJSON_CreateArray();
    cJSON_AddItemToObject(out, "tasks", tasks_dup);

    char* json = cJSON_PrintUnformatted(out);
    cJSON_Delete(out);
    free(content);
    cJSON_Delete(listed_root);
    free(listed);
    return json;
}
