#include <jni.h>
#include <string.h>
#include <malloc.h>
#include <set_raw_transaction_for_btc.h>
#include <cmd_sign.h>
#include "klaytn.h"
#include "sodium.h"
#include "bip32.h"
#include "bip39.h"
#include <base/curves.h>
#include "coldwallet_did_indy_create_key_msg.h"
#include "process_cmd_private.h"
#include <spay_pin_random_util_tl.h>
#include <coldwallet_api.h>
#include <coldwallet_did_indy_prepare_protected_auth_crypt_msg.h>
#include "coldwallet_api.h"
#include "coldwallet_did_indy_encrypt_db_key_msg.h"
#include "coldwallet_did_indy_decrypt_db_key_msg.h"
#include "coldwallet_crypto_tl.h"


#define MAX_STRING_LENGTH 51200

JNIEXPORT bool JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_parseBTCTransaction(JNIEnv *env,
                                                                                        jclass type,
                                                                                        jbyteArray payload,
                                                                                        jint payload_len) {

    jbyte *_payload = (*env)->GetByteArrayElements(env, payload, NULL);

    btc_tx *tx = btc_tx_new();
    bool result = btc_tx_deserialize((uint8_t *) _payload, (uint32_t) payload_len, tx, NULL, true);

    btc_tx_free(tx);
    return result;
}


JNIEXPORT void JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testLibSodiumSignWithHDNode(
        JNIEnv *env, jclass type) {

    TTY_LOG("test Lib Sodium");
    unsigned char pk[crypto_sign_ed25519_PUBLICKEYBYTES] = {0,};
    unsigned char sk[crypto_sign_ed25519_SECRETKEYBYTES] = {0,};
    crypto_sign_keypair(pk, sk);

    unsigned char signedMessage[64] = {0,};
    unsigned long long sigLength;
    unsigned char message[20] = "HelloWorld\0";
    unsigned long long int length = sizeof(message);
    crypto_sign_detached(signedMessage, &sigLength, message, length, sk);

    int result = crypto_sign_verify_detached(signedMessage, message, length, pk);
    TTY_LOG("Test Result: %d\n", result);


    uint8_t seed[COLDWALLET_SEED_SIZE];
    int num_of_words = 12;
    TTY_LOG("generate_key : input param check pass");

    // initial
    memset(seed, 0, COLDWALLET_SEED_SIZE);

    const char mnemonic[COLDWALLET_MAX_MNEMONIC_SIZE] = "ask ask ask ask ask ask sad sad sad sad sad ask";

    int words_len = ((num_of_words * 8) * 4) / 3;
    mnemonic_to_seed(mnemonic, "", seed, NULL);

    TTY_LOG("Get public key");

    uint8_t publickey[COLDWALLET_COMPRESSED_PUBLICKEY_SIZE];
    uint8_t privatekey[COLDWALLET_PRIVATEKEY_SIZE];
    uint32_t path[4] = {
            0x6d,
            0x8000002C,
            0x80000094,
            0x80000000
    };

    for (int i = 0; i < 4; i++) {
        TTY_LOG("%x", path[i]);
    }

    HDNode node;
    memset((void *) &node, 0, sizeof(node));

    TTY_LOG("dump seed");
    dump(seed, COLDWALLET_SEED_SIZE);

    if (!hdnode_from_seed(seed, COLDWALLET_SEED_SIZE, ED25519_NAME, &node)) {
        TTY_LOG("node.private_key generating failed");
    }
    TTY_LOG("sign_xlm : get hdnode from seed pass");


    get_ex_key(seed, path, 4, privatekey, NULL, publickey);

    memcpy(node.public_key, publickey, sizeof(publickey));
    memcpy(node.private_key, privatekey, sizeof(privatekey));

    dump(publickey, 33);


    hdnode_sign(&node, message, sizeof(message), node.curve->hasher_sign, signedMessage, 0, 0);
    // pub : node->public_key + 1
    // node.private_key => seed

    uint8_t *publicKeyTest = node.public_key + 1;
    // node's private key equal seed.
    crypto_sign_ed25519_seed_keypair(pk, sk, node.private_key);
    int compResult = memcmp(publicKeyTest, pk, 32);
    TTY_LOG("comp result :%d", compResult);

    dump(pk, 32);


    result = crypto_sign_verify_detached(signedMessage, message, length, publicKeyTest);
    TTY_LOG("Test hd path sign Result: %d\n", result);

}

uint32_t get_wrapped_key(in_data_t *wrapped_keyinfo) {
    const char mnemonic[COLDWALLET_MAX_MNEMONIC_SIZE] = "ask ask ask ask ask ask sad sad sad sad sad ask";
    uint8_t seed[COLDWALLET_SEED_SIZE] = {0,};
    uint8_t tmp_buf[COLDWALLET_IN_DATA_MAX_LEN];
    uint8_t rand_value[SPAY_RANDOM_NUMBER_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                                                   0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
                                                   0x11, 0x12, 0x13, 0x14};
    key_info_t keyinfo;

    if (!is_pin_random_loaded()) {
        set_pin_random(rand_value, SPAY_RANDOM_NUMBER_SIZE);
    }

    uint32_t tmp_buf_len;

    memset(seed, 0, sizeof(seed));

    mnemonic_to_seed((const char *) mnemonic, "", seed, NULL);

    TTY_LOG("import_bckey : mnemonic_to_seed pass");

    TTY_LOG("get_wrapped_key seed dump");
    dump(seed, COLDWALLET_SEED_SIZE);

    memset(tmp_buf, 0, sizeof(tmp_buf));
    tmp_buf_len = sizeof(tmp_buf);


    memcpy(keyinfo.mnemonic, mnemonic, COLDWALLET_MAX_MNEMONIC_SIZE);
    memcpy(keyinfo.seed, seed, COLDWALLET_SEED_SIZE);

    /* encrypt using DSK and wrap */
    uint32_t ret_val = coldwallet_sk_encrypt_wrap(CSTY_KEY,
                                                  (uint8_t *) &keyinfo, sizeof(keyinfo),
                                                  tmp_buf, &tmp_buf_len);
    TTY_LOG("temp buf len %d", tmp_buf_len);
    dump(tmp_buf, tmp_buf_len);

    wrapped_keyinfo->len = tmp_buf_len;

    memcpy(wrapped_keyinfo->blob, tmp_buf, tmp_buf_len);

    TZ_bzero(seed, sizeof(seed));
    TZ_bzero(tmp_buf, sizeof(tmp_buf));

    return ret_val;
}


JNIEXPORT jobject JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testIndyCryptoSign(JNIEnv *env,
                                                                                       jclass type,
                                                                                       jint mode,
                                                                                       jbyteArray encrypted_ed25519_seed,
                                                                                       jint encrypted_ed25519_seed_len,
                                                                                       jint hd_path_last_idx,
                                                                                       jbyteArray message,
                                                                                       jint message_len) {
    TTY_LOG("## start test indy crypto sign ##");

    jbyte *_payload = (*env)->GetByteArrayElements(env, encrypted_ed25519_seed, NULL);
    jbyte *_mesage = (*env)->GetByteArrayElements(env, message, NULL);

    tz_did_indy_crypto_sign_cmd_t input;
    tz_did_indy_crypto_sign_payload_t output;

    // fill-out input
    memset(&input, 0, sizeof(input));
    memset(&output, 0, sizeof(output));

    input.keypair_material.mode = (uint32_t) mode;
    input.keypair_material.ed25519_seed.encrypted.len = (uint32_t) encrypted_ed25519_seed_len;
    memcpy(input.keypair_material.ed25519_seed.encrypted.blob, _payload,
           (uint32_t) encrypted_ed25519_seed_len);
    memcpy(input.message.blob, _mesage, (uint32_t) message_len);
    input.message.len = (uint32_t) message_len;
    input.keypair_material.hd_path_last_index = (uint32_t) hd_path_last_idx;

    get_wrapped_key(&input.keypair_material.wrapped_keyinfo);

    uint32_t retValue = did_indy_crypto_sign(&input, &output);

    jclass class = (*env)->FindClass(env,
                                     "com/samsung/android/blockchainnativelibs/ResultCryptoSign");
    jmethodID cid = (*env)->GetMethodID(env, class, "<init>", "()V");
    jobject newObject = (*env)->NewObject(env, class, cid, "()V");


    // set result
    jfieldID fid = (*env)->GetFieldID(env, class, "result", "I");
    int32_t result = retValue;
    (*env)->SetIntField(env, newObject, fid, result);


    // set signed message
    jbyteArray jbyte_signed_message = (*env)->NewByteArray(env,
                                                           sizeof(output.payload.resp.signed_message.blob));
    (*env)->SetByteArrayRegion(env, jbyte_signed_message, 0,
                               sizeof(output.payload.resp.signed_message.blob),
                               (const jbyte *) output.payload.resp.signed_message.blob);

    fid = (*env)->GetFieldID(env, class, "signed_message", "[B");
    (*env)->SetObjectField(env, newObject, fid, jbyte_signed_message);

    TTY_LOG("dump output.payload.resp.signed_message");
    dump(output.payload.resp.signed_message.blob, 64);


    (*env)->DeleteLocalRef(env, jbyte_signed_message);

    return newObject;
}

JNIEXPORT jobject JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testIndyCreateKey(JNIEnv *env,
                                                                                      jclass type,
                                                                                      jint mode,
                                                                                      jbyteArray seed_info,
                                                                                      jint seed_info_len,
                                                                                      jint hd_path_last_idx) {
    TTY_LOG("## start test indy create key ##");

    jbyte *_payload = (*env)->GetByteArrayElements(env, seed_info, NULL);

    tz_did_indy_create_key_cmd_t input;
    tz_did_indy_create_key_payload_t output;

    // fill-out input
    input.keypair_material.mode = (uint32_t) mode;
    input.keypair_material.ed25519_seed.plain.len = (uint32_t) seed_info_len;
    memcpy(input.keypair_material.ed25519_seed.plain.blob, _payload, (uint32_t) seed_info_len);
    input.keypair_material.hd_path_last_index = (uint32_t) hd_path_last_idx;

    get_wrapped_key(&input.keypair_material.wrapped_keyinfo);

    uint32_t retValue = did_indy_create_key(&input, &output);


    jclass class = (*env)->FindClass(env,
                                     "com/samsung/android/blockchainnativelibs/ResultCreateKey");

    jmethodID cid = (*env)->GetMethodID(env, class, "<init>", "()V");
    jobject newObject = (*env)->NewObject(env, class, cid, "()V");


    // set result
    jfieldID fid = (*env)->GetFieldID(env, class, "result", "I");
    int32_t result = retValue;
    (*env)->SetIntField(env, newObject, fid, result);


    // set encrypted seed info result
    jbyteArray jbyte_encrypted_seed = (*env)->NewByteArray(env,
                                                           output.payload.resp.encrypted_ed25519_seed.len);
    (*env)->SetByteArrayRegion(env, jbyte_encrypted_seed, 0,
                               output.payload.resp.encrypted_ed25519_seed.len,
                               (const jbyte *) output.payload.resp.encrypted_ed25519_seed.blob);

    fid = (*env)->GetFieldID(env, class, "encryptedSeed", "[B");
    (*env)->SetObjectField(env, newObject, fid, jbyte_encrypted_seed);

    TTY_LOG("dump output.payload.resp.public_key");
    dump(output.payload.resp.public_key.blob, 32);

    // set ver key key
    jbyteArray jbyte_ver_key = (*env)->NewByteArray(env, output.payload.resp.public_key.len);
    (*env)->SetByteArrayRegion(env, jbyte_ver_key, 0, output.payload.resp.public_key.len,
                               (const jbyte *) output.payload.resp.public_key.blob);

    fid = (*env)->GetFieldID(env, class, "publicKey", "[B");
    (*env)->SetObjectField(env, newObject, fid, jbyte_ver_key);

    (*env)->DeleteLocalRef(env, jbyte_encrypted_seed);
    (*env)->DeleteLocalRef(env, jbyte_ver_key);

    return newObject;
}

JNIEXPORT jobject JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testEncryptDbKey(JNIEnv *env,
                                                                                     jclass type,
                                                                                     jbyteArray message,
                                                                                     int messageLength) {
    TTY_LOG("## start test indy encrypt db key ##");

    jbyte *_payload = (*env)->GetByteArrayElements(env, message, NULL);

    tz_did_indy_encrypt_db_key_cmd_t input;
    tz_did_indy_encrypt_db_key_payload_t output;

//    // fill-out input
    memcpy(input.message.blob, _payload, messageLength);
    input.message.len = messageLength;
    get_wrapped_key(&input.wrapped_keyinfo);

    uint32_t retValue = did_indy_encrypt_db_key(&input, &output);

    jclass class = (*env)->FindClass(env,
                                     "com/samsung/android/blockchainnativelibs/ResultEncryptDbKey");
    jmethodID cid = (*env)->GetMethodID(env, class, "<init>", "()V");
    jobject newObject = (*env)->NewObject(env, class, cid, "()V");


    // set result
    int32_t result = retValue;
    jfieldID fid = (*env)->GetFieldID(env, class, "result", "I");
    (*env)->SetIntField(env, newObject, fid, result);


    jbyteArray jbyte_encrypted_key = (*env)->NewByteArray(env,
                                                          output.payload.resp.encrypted_message.len);
    (*env)->SetByteArrayRegion(env, jbyte_encrypted_key, 0,
                               output.payload.resp.encrypted_message.len,
                               (const jbyte *) output.payload.resp.encrypted_message.blob);

    fid = (*env)->GetFieldID(env, class, "encryptedDbKey", "[B");
    (*env)->SetObjectField(env, newObject, fid, jbyte_encrypted_key);

    (*env)->DeleteLocalRef(env, jbyte_encrypted_key);

    return newObject;
}

JNIEXPORT jobject JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testDecryptDbKey(JNIEnv *env,
                                                                                     jclass type,
                                                                                     jbyteArray encryptedMessage,
                                                                                     int messageLength) {
    TTY_LOG("## start test indy decrypt db key ##");

    jbyte *_payload = (*env)->GetByteArrayElements(env, encryptedMessage, NULL);

    tz_did_indy_decrypt_db_key_cmd_t input;
    tz_did_indy_decrypt_db_key_payload_t output;

//    // fill-out input
    memcpy(input.encrypted_message.blob, _payload, messageLength);
    input.encrypted_message.len = messageLength;
    get_wrapped_key(&input.wrapped_keyinfo);

    uint32_t retValue = did_indy_decrypt_db_key(&input, &output);

    jclass class = (*env)->FindClass(env,
                                     "com/samsung/android/blockchainnativelibs/ResultDecryptDbKey");
    jmethodID cid = (*env)->GetMethodID(env, class, "<init>", "()V");
    jobject newObject = (*env)->NewObject(env, class, cid, "()V");


    // set result
    int32_t result = retValue;
    jfieldID fid = (*env)->GetFieldID(env, class, "result", "I");
    (*env)->SetIntField(env, newObject, fid, result);


    jbyteArray jbyte_decrypted_key = (*env)->NewByteArray(env,
                                                          output.payload.resp.message.len);
    (*env)->SetByteArrayRegion(env, jbyte_decrypted_key, 0,
                               output.payload.resp.message.len,
                               (const jbyte *) output.payload.resp.message.blob);

    fid = (*env)->GetFieldID(env, class, "DbKey", "[B");
    (*env)->SetObjectField(env, newObject, fid, jbyte_decrypted_key);

    (*env)->DeleteLocalRef(env, jbyte_decrypted_key);

    return newObject;
}

JNIEXPORT bool JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testPrepareProtectedAuthCrypt(
        JNIEnv *env, jclass type,
        jint mode,
        jbyteArray encrypted_ed25519_seed,
        jint encrypted_ed25519_seed_len,
        jint hd_path_last_idx,
        jbyteArray sender_verkey,
        jint sender_verkey_len,
        jbyteArray recipient_verkey_list,
        jint recipient_number) {

    tz_did_indy_prepare_protected_auth_crypt_cmd_t input;
    tz_did_indy_prepare_protected_auth_crypt_payload_t output;


    // fill-out input
    input.keypair_material.mode = (uint32_t) mode;

    jbyte *_payload = (*env)->GetByteArrayElements(env, encrypted_ed25519_seed, NULL);
    memcpy(input.keypair_material.ed25519_seed.encrypted.blob, _payload, (uint32_t) encrypted_ed25519_seed_len);
    input.keypair_material.ed25519_seed.encrypted.len = (uint32_t) encrypted_ed25519_seed_len;

    input.keypair_material.hd_path_last_index = (uint32_t) hd_path_last_idx;
    get_wrapped_key(&input.keypair_material.wrapped_keyinfo);

    jbyte *_sender_verkey = (*env)->GetByteArrayElements(env, sender_verkey, NULL);
    memcpy(input.sender_public_key.blob, _sender_verkey, (uint32_t) sender_verkey_len);
    input.sender_public_key.len = (uint32_t) sender_verkey_len;

    jbyte *_recipient_verkey_list = (*env)->GetByteArrayElements(env, recipient_verkey_list, NULL);
    uint8_t recipient_list_buf[sender_verkey_len * 8];
    memcpy(recipient_list_buf, _recipient_verkey_list, sizeof(recipient_list_buf));

    int i;
    for (i = 0; i < recipient_number; i++) {
        memcpy(input.recipient_public_key_array[i].blob, recipient_list_buf + i * sender_verkey_len,
               (uint32_t) sender_verkey_len);
        input.recipient_public_key_array[i].len = (uint32_t) sender_verkey_len;
    }
    input.recipient_number = (uint32_t) recipient_number;

    uint32_t retValue = did_indy_prepare_protected_auth_crypt(&input, &output);

    // todo deserialize response. it will be checked DidTaTest.java

    return retValue == 0;
}


JNIEXPORT bool JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testEncryptPlainText(
        JNIEnv *env, jclass type,
        jbyteArray message,
        jint message_len,
        jbyteArray aad,
        jint aad_len) {

    tz_did_indy_encrypt_plain_text_cmd_t input;
    tz_did_indy_encrypt_plain_text_payload_t output;


    // fill-out input
    int ret_value = did_indy_encrypt_plain_text(&input, &output);

    return ret_value == 0;
}


JNIEXPORT void JNICALL
Java_com_samsung_android_blockchainnativelibs_BcTaNativeTestHandler_testTZVendorRandom(JNIEnv *env,
                                                                                       jclass type) {
    uint8_t randomBuffer[8];
    uint32_t size = 8;

    for (int i = 0; i < 10; ++i) {
        TTY_LOG("make random try %d", i);
        TZ_gen_rand_data(randomBuffer, &size);
        TTY_LOG("%d %d %d %d %d %d %d %d", randomBuffer[0], randomBuffer[1], randomBuffer[2],
                randomBuffer[3], randomBuffer[4], randomBuffer[5], randomBuffer[6],
                randomBuffer[7]);
    }

}
