#include "../../secp256k1/include/secp256k1.h"
#include "../../secp256k1/include/secp256k1_schnorrsig.h"
#include "../../secp256k1/include/secp256k1_ecdh.h"
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include "../nostr_platform.h"

/*
 * PRIVATE INTERNAL FUNCTIONS - NOT EXPORTED
 * These functions are for internal library use only.
 */

/** Opaque data structure that holds a parsed and valid public key.
 *  Guaranteed to be 64 bytes in size, and can be safely copied/moved.
 */
typedef struct nostr_secp256k1_pubkey {
    unsigned char data[64];
} nostr_secp256k1_pubkey;

/** Opaque data structure that holds a parsed keypair.
 *  Guaranteed to be 96 bytes in size, and can be safely copied/moved.
 */
typedef struct nostr_secp256k1_keypair {
    unsigned char data[96];
} nostr_secp256k1_keypair;

/** Opaque data structure that holds a parsed x-only public key.
 *  Guaranteed to be 64 bytes in size, and can be safely copied/moved.
 */
typedef struct nostr_secp256k1_xonly_pubkey {
    unsigned char data[64];
} nostr_secp256k1_xonly_pubkey;

// Global context for secp256k1 operations
static secp256k1_context* g_ctx = NULL;

int nostr_secp256k1_context_create(void) {
    if (g_ctx != NULL) {
        return 1; // Already initialized
    }
    
    g_ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    if (g_ctx == NULL) {
        return 0;
    }
    
    // Add randomization to the context using platform entropy.
    unsigned char randomize[32];
    if (nostr_platform_random(randomize, sizeof(randomize)) != 0) {
        secp256k1_context_destroy(g_ctx);
        g_ctx = NULL;
        return 0;
    }

    if (!secp256k1_context_randomize(g_ctx, randomize)) {
        secp256k1_context_destroy(g_ctx);
        g_ctx = NULL;
        return 0;
    }
    
    return 1;
}

void nostr_secp256k1_context_destroy(void) {
    if (g_ctx != NULL) {
        secp256k1_context_destroy(g_ctx);
        g_ctx = NULL;
    }
}

int nostr_secp256k1_ec_seckey_verify(const unsigned char *seckey) {
    if (g_ctx == NULL || seckey == NULL) {
        return 0;
    }
    
    return secp256k1_ec_seckey_verify(g_ctx, seckey);
}

int nostr_secp256k1_ec_pubkey_create(nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey) {
    if (g_ctx == NULL || pubkey == NULL || seckey == NULL) {
        return 0;
    }
    
    secp256k1_pubkey internal_pubkey;
    if (!secp256k1_ec_pubkey_create(g_ctx, &internal_pubkey, seckey)) {
        return 0;
    }
    
    // Copy the internal representation to our wrapper
    memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
    
    return 1;
}

int nostr_secp256k1_keypair_create(nostr_secp256k1_keypair *keypair, const unsigned char *seckey) {
    if (g_ctx == NULL || keypair == NULL || seckey == NULL) {
        return 0;
    }
    
    secp256k1_keypair internal_keypair;
    if (!secp256k1_keypair_create(g_ctx, &internal_keypair, seckey)) {
        return 0;
    }
    
    // Copy the internal representation to our wrapper
    memcpy(keypair->data, &internal_keypair, sizeof(secp256k1_keypair));
    
    return 1;
}

int nostr_secp256k1_keypair_xonly_pub(nostr_secp256k1_xonly_pubkey *pubkey, const nostr_secp256k1_keypair *keypair) {
    if (g_ctx == NULL || pubkey == NULL || keypair == NULL) {
        return 0;
    }
    
    secp256k1_keypair internal_keypair;
    secp256k1_xonly_pubkey internal_xonly;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_keypair, keypair->data, sizeof(secp256k1_keypair));
    
    if (!secp256k1_keypair_xonly_pub(g_ctx, &internal_xonly, NULL, &internal_keypair)) {
        return 0;
    }
    
    // Copy the internal representation to our wrapper
    memcpy(pubkey->data, &internal_xonly, sizeof(secp256k1_xonly_pubkey));
    
    return 1;
}

int nostr_secp256k1_xonly_pubkey_parse(nostr_secp256k1_xonly_pubkey *pubkey, const unsigned char *input32) {
    if (g_ctx == NULL || pubkey == NULL || input32 == NULL) {
        return 0;
    }
    
    secp256k1_xonly_pubkey internal_xonly;
    if (!secp256k1_xonly_pubkey_parse(g_ctx, &internal_xonly, input32)) {
        return 0;
    }
    
    // Copy the internal representation to our wrapper
    memcpy(pubkey->data, &internal_xonly, sizeof(secp256k1_xonly_pubkey));
    
    return 1;
}

int nostr_secp256k1_xonly_pubkey_serialize(unsigned char *output32, const nostr_secp256k1_xonly_pubkey *pubkey) {
    if (g_ctx == NULL || output32 == NULL || pubkey == NULL) {
        return 0;
    }
    
    secp256k1_xonly_pubkey internal_xonly;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_xonly, pubkey->data, sizeof(secp256k1_xonly_pubkey));
    
    return secp256k1_xonly_pubkey_serialize(g_ctx, output32, &internal_xonly);
}

int nostr_secp256k1_schnorrsig_sign32(unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
    if (g_ctx == NULL || sig64 == NULL || msghash32 == NULL || keypair == NULL) {
        return 0;
    }
    
    secp256k1_keypair internal_keypair;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_keypair, keypair->data, sizeof(secp256k1_keypair));
    
    return secp256k1_schnorrsig_sign32(g_ctx, sig64, msghash32, &internal_keypair, aux_rand32);
}

int nostr_secp256k1_schnorrsig_verify(const unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_xonly_pubkey *pubkey) {
    if (g_ctx == NULL || sig64 == NULL || msghash32 == NULL || pubkey == NULL) {
        return 0;
    }
    
    secp256k1_xonly_pubkey internal_xonly;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_xonly, pubkey->data, sizeof(secp256k1_xonly_pubkey));
    
    return secp256k1_schnorrsig_verify(g_ctx, sig64, msghash32, 32, &internal_xonly);
}

int nostr_secp256k1_ec_pubkey_serialize_compressed(unsigned char *output, const nostr_secp256k1_pubkey *pubkey) {
    if (g_ctx == NULL || output == NULL || pubkey == NULL) {
        return 0;
    }
    
    secp256k1_pubkey internal_pubkey;
    size_t outputlen = 33;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
    
    return secp256k1_ec_pubkey_serialize(g_ctx, output, &outputlen, &internal_pubkey, SECP256K1_EC_COMPRESSED);
}

int nostr_secp256k1_ec_seckey_tweak_add(unsigned char *seckey, const unsigned char *tweak) {
    if (g_ctx == NULL || seckey == NULL || tweak == NULL) {
        return 0;
    }
    
    return secp256k1_ec_seckey_tweak_add(g_ctx, seckey, tweak);
}

int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey *pubkey, const unsigned char *input, size_t inputlen) {
    if (g_ctx == NULL || pubkey == NULL || input == NULL) {
        return 0;
    }
    
    secp256k1_pubkey internal_pubkey;
    if (!secp256k1_ec_pubkey_parse(g_ctx, &internal_pubkey, input, inputlen)) {
        return 0;
    }
    
    // Copy the internal representation to our wrapper
    memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
    
    return 1;
}

int nostr_secp256k1_ecdh(unsigned char *result, const nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey, void *hashfp, void *data) {
    if (g_ctx == NULL || result == NULL || pubkey == NULL || seckey == NULL) {
        return 0;
    }
    
    secp256k1_pubkey internal_pubkey;
    
    // Copy from our wrapper to internal representation
    memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
    
    return secp256k1_ecdh(g_ctx, result, &internal_pubkey, seckey, hashfp, data);
}

int nostr_secp256k1_ec_pubkey_tweak_mul(nostr_secp256k1_pubkey* pubkey, const unsigned char* tweak32) {
    if (g_ctx == NULL || pubkey == NULL || tweak32 == NULL) {
        return 0;
    }

    secp256k1_pubkey internal_pubkey;
    memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));

    if (!secp256k1_ec_pubkey_tweak_mul(g_ctx, &internal_pubkey, tweak32)) {
        return 0;
    }

    memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
    return 1;
}

int nostr_secp256k1_ec_pubkey_negate(nostr_secp256k1_pubkey* pubkey) {
    if (g_ctx == NULL || pubkey == NULL) {
        return 0;
    }

    secp256k1_pubkey internal_pubkey;
    memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));

    if (!secp256k1_ec_pubkey_negate(g_ctx, &internal_pubkey)) {
        return 0;
    }

    memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
    return 1;
}

int nostr_secp256k1_ec_pubkey_combine(nostr_secp256k1_pubkey* out,
                                      const nostr_secp256k1_pubkey* const* ins,
                                      size_t n) {
    if (g_ctx == NULL || out == NULL || ins == NULL || n == 0) {
        return 0;
    }

    secp256k1_pubkey* parsed = (secp256k1_pubkey*)calloc(n, sizeof(secp256k1_pubkey));
    const secp256k1_pubkey** ptrs = (const secp256k1_pubkey**)calloc(n, sizeof(secp256k1_pubkey*));
    if (!parsed || !ptrs) {
        free(parsed);
        free(ptrs);
        return 0;
    }

    for (size_t i = 0; i < n; i++) {
        if (!ins[i]) {
            free(parsed);
            free(ptrs);
            return 0;
        }
        memcpy(&parsed[i], ins[i]->data, sizeof(secp256k1_pubkey));
        ptrs[i] = &parsed[i];
    }

    secp256k1_pubkey combined;
    int ok = secp256k1_ec_pubkey_combine(g_ctx, &combined, ptrs, n);

    free(parsed);
    free(ptrs);

    if (!ok) {
        return 0;
    }

    memcpy(out->data, &combined, sizeof(secp256k1_pubkey));
    return 1;
}

int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len) {
    if (buf == NULL || len == 0) {
        return 0;
    }
    
    if (nostr_platform_random(buf, len) != 0) {
        return 0;
    }

    return 1;
}
