#define _POSIX_C_SOURCE 200809L

#include "tools_internal.h"

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

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

/* Unified signer_crypto tool — exposes all algorithm-based n_signer verbs
 * through a single tool with an "operation" enum. Requires a remote n_signer;
 * returns a clear error in local mode since the local backend does not support
 * algorithm-based verbs (PQ crypto, OTP, etc.). */
char* execute_signer_crypto(tools_context_t* ctx, const char* args_json) {
    if (!ctx) return json_error_local("tool context unavailable");

    nostr_signer_t* signer = ctx->signer;
    if (!signer) {
        /* Fall back to the process-lifetime signer if the tools context
         * doesn't have one wired (e.g. during startup). */
        signer = nostr_handler_get_signer();
    }
    if (!signer) {
        return json_error_local("signer_crypto requires a remote n_signer "
                                "(local mode does not support algorithm-based verbs)");
    }

    cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
    if (!args) return json_error_local("invalid arguments JSON");

    cJSON* op_json = cJSON_GetObjectItemCaseSensitive(args, "operation");
    if (!op_json || !cJSON_IsString(op_json) || !op_json->valuestring || op_json->valuestring[0] == '\0') {
        cJSON_Delete(args);
        return json_error_local("signer_crypto requires a string 'operation' field");
    }
    const char* operation = op_json->valuestring;

    cJSON* algorithm = cJSON_GetObjectItemCaseSensitive(args, "algorithm");
    const char* alg_str = (algorithm && cJSON_IsString(algorithm) && algorithm->valuestring)
                              ? algorithm->valuestring : NULL;
    cJSON* index_json = cJSON_GetObjectItemCaseSensitive(args, "index");
    int index = (index_json && cJSON_IsNumber(index_json)) ? (int)index_json->valuedouble : 0;
    cJSON* scheme_json = cJSON_GetObjectItemCaseSensitive(args, "scheme");
    const char* scheme = (scheme_json && cJSON_IsString(scheme_json) && scheme_json->valuestring)
                             ? scheme_json->valuestring : NULL;

    cJSON* result = cJSON_CreateObject();
    if (!result) { cJSON_Delete(args); return NULL; }
    cJSON_AddBoolToObject(result, "success", 1);
    cJSON_AddStringToObject(result, "operation", operation);

    int rc;
    char* result_str = NULL;
    cJSON* info_out = NULL;
    int valid_out = 0;

    if (strcmp(operation, "get_info") == 0) {
        rc = nostr_signer_get_info(signer, &info_out);
        if (rc == NOSTR_SUCCESS && info_out) {
            char* info_json = cJSON_PrintUnformatted(info_out);
            cJSON_AddStringToObject(result, "info", info_json ? info_json : "{}");
            free(info_json);
            cJSON_Delete(info_out);
        } else {
            const char* le = nostr_signer_last_error(signer);
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "get_info failed");
        }
    } else if (strcmp(operation, "get_public_key") == 0) {
        if (!alg_str) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "get_public_key requires 'algorithm' (e.g. secp256k1, ed25519, ml-dsa-65)");
        } else {
            rc = nostr_signer_get_public_key_alg(signer, alg_str, index, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "get_public_key failed");
            }
        }
    } else if (strcmp(operation, "sign") == 0) {
        cJSON* msg_json = cJSON_GetObjectItemCaseSensitive(args, "message_hex");
        if (!msg_json || !cJSON_IsString(msg_json) || !msg_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "sign requires 'message_hex' (hex-encoded message bytes)");
        } else if (!alg_str) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "sign requires 'algorithm' (e.g. secp256k1, ed25519, ml-dsa-65, slh-dsa-128s)");
        } else {
            size_t msg_hex_len = strlen(msg_json->valuestring);
            size_t msg_bytes_len = msg_hex_len / 2;
            unsigned char* msg_bytes = (unsigned char*)malloc(msg_bytes_len + 1);
            if (!msg_bytes) {
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", "memory allocation failed");
            } else if (nostr_hex_to_bytes(msg_json->valuestring, msg_bytes, (int)msg_bytes_len) != 0) {
                free(msg_bytes);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", "invalid message_hex");
            } else {
                rc = nostr_signer_sign(signer, alg_str, index, scheme, msg_bytes, msg_bytes_len, &result_str);
                free(msg_bytes);
                if (rc == NOSTR_SUCCESS && result_str) {
                    cJSON_AddStringToObject(result, "result", result_str);
                    free(result_str);
                } else {
                    const char* le = nostr_signer_last_error(signer);
                    cJSON_AddBoolToObject(result, "success", 0);
                    cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "sign failed");
                }
            }
        }
    } else if (strcmp(operation, "verify") == 0) {
        cJSON* msg_json = cJSON_GetObjectItemCaseSensitive(args, "message_hex");
        cJSON* sig_json = cJSON_GetObjectItemCaseSensitive(args, "signature_hex");
        if (!msg_json || !cJSON_IsString(msg_json) || !msg_json->valuestring ||
            !sig_json || !cJSON_IsString(sig_json) || !sig_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "verify requires 'message_hex' and 'signature_hex'");
        } else if (!alg_str) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "verify requires 'algorithm'");
        } else {
            size_t msg_hex_len = strlen(msg_json->valuestring);
            size_t msg_bytes_len = msg_hex_len / 2;
            unsigned char* msg_bytes = (unsigned char*)malloc(msg_bytes_len + 1);
            size_t sig_hex_len = strlen(sig_json->valuestring);
            size_t sig_bytes_len = sig_hex_len / 2;
            unsigned char* sig_bytes = (unsigned char*)malloc(sig_bytes_len + 1);
            if (!msg_bytes || !sig_bytes) {
                free(msg_bytes); free(sig_bytes);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", "memory allocation failed");
            } else if (nostr_hex_to_bytes(msg_json->valuestring, msg_bytes, (int)msg_bytes_len) != 0 ||
                       nostr_hex_to_bytes(sig_json->valuestring, sig_bytes, (int)sig_bytes_len) != 0) {
                free(msg_bytes); free(sig_bytes);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", "invalid hex in message_hex or signature_hex");
            } else {
                rc = nostr_signer_verify(signer, alg_str, index, scheme,
                                         msg_bytes, msg_bytes_len,
                                         sig_bytes, sig_bytes_len,
                                         &valid_out);
                free(msg_bytes); free(sig_bytes);
                if (rc == NOSTR_SUCCESS) {
                    cJSON_AddBoolToObject(result, "valid", valid_out);
                } else {
                    const char* le = nostr_signer_last_error(signer);
                    cJSON_AddBoolToObject(result, "success", 0);
                    cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "verify failed");
                }
            }
        }
    } else if (strcmp(operation, "encapsulate") == 0) {
        cJSON* peer_json = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey_hex");
        if (!peer_json || !cJSON_IsString(peer_json) || !peer_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "encapsulate requires 'peer_pubkey_hex' (ML-KEM-768 public key)");
        } else {
            rc = nostr_signer_encapsulate(signer, peer_json->valuestring, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "encapsulate failed");
            }
        }
    } else if (strcmp(operation, "decapsulate") == 0) {
        cJSON* ct_json = cJSON_GetObjectItemCaseSensitive(args, "ciphertext_hex");
        if (!ct_json || !cJSON_IsString(ct_json) || !ct_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "decapsulate requires 'ciphertext_hex' and 'index'");
        } else {
            rc = nostr_signer_decapsulate(signer, index, ct_json->valuestring, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "decapsulate failed");
            }
        }
    } else if (strcmp(operation, "derive_shared_secret") == 0) {
        cJSON* peer_json = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey_hex");
        if (!peer_json || !cJSON_IsString(peer_json) || !peer_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "derive_shared_secret requires 'peer_pubkey_hex' (X25519) and 'index'");
        } else {
            rc = nostr_signer_derive_shared_secret(signer, index, peer_json->valuestring, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "derive_shared_secret failed");
            }
        }
    } else if (strcmp(operation, "derive_hmac") == 0) {
        cJSON* data_json = cJSON_GetObjectItemCaseSensitive(args, "data");
        if (!data_json || !cJSON_IsString(data_json) || !data_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "derive_hmac requires 'data' (UTF-8 string)");
        } else {
            rc = nostr_signer_derive_hmac(signer, data_json->valuestring, (char[65]){0});
            if (rc == NOSTR_SUCCESS) {
                char digest_hex[65] = {0};
                nostr_signer_derive_hmac(signer, data_json->valuestring, digest_hex);
                cJSON_AddStringToObject(result, "digest_hex", digest_hex);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "derive_hmac failed");
            }
        }
    } else if (strcmp(operation, "otp_encrypt") == 0) {
        cJSON* pt_json = cJSON_GetObjectItemCaseSensitive(args, "plaintext_b64");
        cJSON* enc_json = cJSON_GetObjectItemCaseSensitive(args, "encoding");
        const char* encoding = (enc_json && cJSON_IsString(enc_json) && enc_json->valuestring)
                                   ? enc_json->valuestring : NULL;
        if (!pt_json || !cJSON_IsString(pt_json) || !pt_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "otp_encrypt requires 'plaintext_b64' (base64-encoded plaintext)");
        } else {
            rc = nostr_signer_otp_encrypt(signer, pt_json->valuestring, encoding, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "otp_encrypt failed");
            }
        }
    } else if (strcmp(operation, "otp_decrypt") == 0) {
        cJSON* ct_json = cJSON_GetObjectItemCaseSensitive(args, "ciphertext");
        cJSON* enc_json = cJSON_GetObjectItemCaseSensitive(args, "encoding");
        const char* encoding = (enc_json && cJSON_IsString(enc_json) && enc_json->valuestring)
                                   ? enc_json->valuestring : NULL;
        if (!ct_json || !cJSON_IsString(ct_json) || !ct_json->valuestring) {
            cJSON_AddBoolToObject(result, "success", 0);
            cJSON_AddStringToObject(result, "error", "otp_decrypt requires 'ciphertext' (ASCII-armored or base64 blob)");
        } else {
            rc = nostr_signer_otp_decrypt(signer, ct_json->valuestring, encoding, &result_str);
            if (rc == NOSTR_SUCCESS && result_str) {
                cJSON_AddStringToObject(result, "result", result_str);
                free(result_str);
            } else {
                const char* le = nostr_signer_last_error(signer);
                cJSON_AddBoolToObject(result, "success", 0);
                cJSON_AddStringToObject(result, "error", (le && le[0]) ? le : "otp_decrypt failed");
            }
        }
    } else {
        cJSON_AddBoolToObject(result, "success", 0);
        cJSON_AddStringToObject(result, "error", "unknown operation");
    }

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