#define _POSIX_C_SOURCE 200809L

#include "tools_internal.h"

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

#include "cjson/cJSON.h"
#include "../config.h"
#include "../llm.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 persist_llm_config_nostr(tools_context_t* ctx, const llm_config_t* cfg, char** out_error) {
    if (!ctx || !ctx->cfg || !cfg) {
        if (out_error) *out_error = strdup("invalid persist context");
        return -1;
    }

    if (out_error) *out_error = NULL;

    cJSON* args = cJSON_CreateObject();
    cJSON* content = cJSON_CreateObject();
    if (!args || !content) {
        cJSON_Delete(args);
        cJSON_Delete(content);
        if (out_error) *out_error = strdup("allocation failure while building llm_config payload");
        return -1;
    }

    cJSON_AddStringToObject(args, "d_tag", "llm_config");
    cJSON_AddStringToObject(content, "provider", cfg->provider);
    cJSON_AddStringToObject(content, "api_key", cfg->api_key);
    cJSON_AddStringToObject(content, "model", cfg->model);
    cJSON_AddStringToObject(content, "base_url", cfg->base_url);
    cJSON_AddNumberToObject(content, "max_tokens", cfg->max_tokens);
    cJSON_AddNumberToObject(content, "temperature", cfg->temperature);
    cJSON_AddItemToObject(args, "content", content);

    char* store_args_json = cJSON_PrintUnformatted(args);
    cJSON_Delete(args);
    if (!store_args_json) {
        if (out_error) *out_error = strdup("failed to serialize llm_config payload");
        return -1;
    }

    char* store_result = execute_config_store(ctx, store_args_json);
    free(store_args_json);
    if (!store_result) {
        if (out_error) *out_error = strdup("config_store returned no response");
        return -1;
    }

    cJSON* root = cJSON_Parse(store_result);
    if (!root || !cJSON_IsObject(root)) {
        cJSON_Delete(root);
        if (out_error) *out_error = strdup("config_store returned invalid JSON");
        free(store_result);
        return -1;
    }

    cJSON* success = cJSON_GetObjectItemCaseSensitive(root, "success");
    if (!success || !cJSON_IsBool(success) || !cJSON_IsTrue(success)) {
        cJSON* err = cJSON_GetObjectItemCaseSensitive(root, "error");
        if (out_error) {
            if (err && cJSON_IsString(err) && err->valuestring) {
                *out_error = strdup(err->valuestring);
            } else {
                *out_error = strdup("config_store failed");
            }
        }
        cJSON_Delete(root);
        free(store_result);
        return -1;
    }

    cJSON_Delete(root);
    free(store_result);
    return 0;
}

static int assign_string_field(cJSON* item, char* dst, size_t dst_size, int* changed) {
    if (!item) return 0;
    if (!cJSON_IsString(item) || !item->valuestring) return -1;
    size_t n = strlen(item->valuestring);
    if (n >= dst_size) return -1;
    memcpy(dst, item->valuestring, n + 1U);
    if (changed) *changed = 1;
    return 0;
}

static void mask_api_key_local(const char* api_key, char* out, size_t out_size) {
    if (!out || out_size == 0) return;
    out[0] = '\0';
    if (!api_key || api_key[0] == '\0') {
        return;
    }

    size_t api_len = strlen(api_key);
    if (api_len >= 8U) {
        snprintf(out,
                 out_size,
                 "%.4s...%s",
                 api_key,
                 api_key + api_len - 4U);
    } else {
        snprintf(out, out_size, "%s", "(set)");
    }
}

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

    llm_config_t cfg;
    if (llm_get_config(&cfg) != 0) {
        return json_error_local("llm runtime unavailable");
    }

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

    char api_key_masked[64] = {0};
    mask_api_key_local(cfg.api_key, api_key_masked, sizeof(api_key_masked));

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "provider", cfg.provider);
    cJSON_AddStringToObject(out, "model", cfg.model);
    cJSON_AddStringToObject(out, "base_url", cfg.base_url);
    cJSON_AddStringToObject(out, "api_key", api_key_masked);
    cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens);
    cJSON_AddNumberToObject(out, "temperature", cfg.temperature);

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

char* execute_model_set(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");

    llm_config_t cfg;
    if (llm_get_config(&cfg) != 0) {
        cJSON_Delete(args);
        return json_error_local("llm runtime unavailable");
    }

    int changed = 0;

    cJSON* provider = cJSON_GetObjectItemCaseSensitive(args, "provider");
    cJSON* api_key = cJSON_GetObjectItemCaseSensitive(args, "api_key");
    cJSON* model = cJSON_GetObjectItemCaseSensitive(args, "model");
    cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url");
    cJSON* max_tokens = cJSON_GetObjectItemCaseSensitive(args, "max_tokens");
    cJSON* temperature = cJSON_GetObjectItemCaseSensitive(args, "temperature");

    if (assign_string_field(provider, cfg.provider, sizeof(cfg.provider), &changed) != 0 ||
        assign_string_field(api_key, cfg.api_key, sizeof(cfg.api_key), &changed) != 0 ||
        assign_string_field(model, cfg.model, sizeof(cfg.model), &changed) != 0 ||
        assign_string_field(base_url, cfg.base_url, sizeof(cfg.base_url), &changed) != 0) {
        cJSON_Delete(args);
        return json_error_local("model_set string field invalid or too long");
    }

    if (max_tokens) {
        if (!cJSON_IsNumber(max_tokens)) {
            cJSON_Delete(args);
            return json_error_local("model_set max_tokens must be a number");
        }
        cfg.max_tokens = (int)max_tokens->valuedouble;
        changed = 1;
    }

    if (temperature) {
        if (!cJSON_IsNumber(temperature)) {
            cJSON_Delete(args);
            return json_error_local("model_set temperature must be a number");
        }
        cfg.temperature = temperature->valuedouble;
        changed = 1;
    }

    cJSON_Delete(args);

    if (!changed) {
        return json_error_local("model_set requires at least one field to update");
    }

    if (llm_set_config(&cfg) != 0) {
        return json_error_local("failed to update runtime llm config");
    }

    ctx->cfg->llm = cfg;

    char* persist_error = NULL;
    int persisted = (persist_llm_config_nostr(ctx, &cfg, &persist_error) == 0) ? 1 : 0;

    cJSON* out = cJSON_CreateObject();
    if (!out) return NULL;
    char api_key_masked[64] = {0};
    mask_api_key_local(cfg.api_key, api_key_masked, sizeof(api_key_masked));

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddStringToObject(out, "provider", cfg.provider);
    cJSON_AddStringToObject(out, "model", cfg.model);
    cJSON_AddStringToObject(out, "base_url", cfg.base_url);
    cJSON_AddStringToObject(out, "api_key", api_key_masked);
    cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens);
    cJSON_AddNumberToObject(out, "temperature", cfg.temperature);
    cJSON_AddBoolToObject(out, "persisted", persisted ? 1 : 0);
    if (!persisted && persist_error && persist_error[0] != '\0') {
        cJSON_AddStringToObject(out, "persist_warning", persist_error);
    }

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

static void append_model_id(cJSON* ids, cJSON* item) {
    if (!ids || !item) return;
    if (cJSON_IsString(item) && item->valuestring) {
        cJSON_AddItemToArray(ids, cJSON_CreateString(item->valuestring));
        return;
    }
    if (cJSON_IsObject(item)) {
        cJSON* id = cJSON_GetObjectItemCaseSensitive(item, "id");
        if (id && cJSON_IsString(id) && id->valuestring) {
            cJSON_AddItemToArray(ids, cJSON_CreateString(id->valuestring));
        }
    }
}

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

    cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url");
    if (base_url && (!cJSON_IsString(base_url) || !base_url->valuestring)) {
        cJSON_Delete(args);
        return json_error_local("model_list base_url must be a string");
    }

    const char* base_url_override = (base_url && base_url->valuestring && base_url->valuestring[0] != '\0')
                                        ? base_url->valuestring
                                        : NULL;

    char* raw = llm_list_models_json(base_url_override);
    cJSON_Delete(args);
    if (!raw) {
        return json_error_local("model_list request failed");
    }

    cJSON* root = cJSON_Parse(raw);
    free(raw);
    if (!root) {
        cJSON_Delete(root);
        return json_error_local("model_list returned invalid JSON");
    }

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

    if (cJSON_IsArray(root)) {
        int n = cJSON_GetArraySize(root);
        for (int i = 0; i < n; i++) {
            append_model_id(ids, cJSON_GetArrayItem(root, i));
        }
    } else if (cJSON_IsObject(root)) {
        cJSON* data = cJSON_GetObjectItemCaseSensitive(root, "data");
        if (data && cJSON_IsArray(data)) {
            int n = cJSON_GetArraySize(data);
            for (int i = 0; i < n; i++) {
                append_model_id(ids, cJSON_GetArrayItem(data, i));
            }
        }
    }

    cJSON_AddBoolToObject(out, "success", 1);
    cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(ids));
    cJSON_AddItemToObject(out, "models", ids);

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