/*
 * Copyright (c) 2026 QPQ IaaS AG <info@qpq.swiss>. All rights reserved.
 * Project: Gajumaru Core Java Libraries <gajumaru.io>
 *
 * This program is dual-licensed:
 * 1) Under the GNU Affero General Public License as published by the Free
 *    Software Foundation, either version 3 of the License, or (at your option)
 *    any later version (AGPL-3.0-or-later).
 *
 * 2) Under a commercial/proprietary license available directly from QPQ IaaS AG.
 *    If you wish to use this software outside the strict constraints of the
 *    AGPLv3 (e.g. within a closed-source or proprietary product), you must
 *    purchase a commercial license from QPQ IaaS AG.
 *
 * Authors:
 *   - Craig Everett <craigeverett@qpq.swiss>
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-QPQ-Commercial
 */

#include <jni.h>
#include <sodium.h>
#include <string.h>

JNIEXPORT void JNICALL
Java_swiss_qpq_gajumobile_security_GajuNative_memzero(JNIEnv *env, jclass clazz, jbyteArray array) {
    if (array == NULL) return;
    jsize len = (*env)->GetArrayLength(env, array);
    jbyte *data = (*env)->GetByteArrayElements(env, array, NULL);
    if (data != NULL) {
        sodium_memzero(data, (size_t) len);
        (*env)->ReleaseByteArrayElements(env, array, data, 0);
    }
}

JNIEXPORT jbyteArray JNICALL
Java_swiss_qpq_gajumobile_security_GajuNative_cryptoSignSeedKeypair(JNIEnv *env, jclass clazz, jbyteArray seed) {
    if (seed == NULL || (*env)->GetArrayLength(env, seed) != crypto_sign_SEEDBYTES) return NULL;

    unsigned char pk[crypto_sign_PUBLICKEYBYTES];
    unsigned char sk[crypto_sign_SECRETKEYBYTES];
    jbyte *seed_data = (*env)->GetByteArrayElements(env, seed, NULL);

    crypto_sign_seed_keypair(pk, sk, (unsigned char *) seed_data);
    (*env)->ReleaseByteArrayElements(env, seed, seed_data, JNI_ABORT);

    jbyteArray result = (*env)->NewByteArray(env, crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES);
    (*env)->SetByteArrayRegion(env, result, 0, crypto_sign_PUBLICKEYBYTES, (jbyte *) pk);
    (*env)->SetByteArrayRegion(env, result, crypto_sign_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES, (jbyte *) sk);

    sodium_memzero(pk, sizeof pk);
    sodium_memzero(sk, sizeof sk);

    return result;
}

JNIEXPORT jbyteArray JNICALL
Java_swiss_qpq_gajumobile_security_GajuNative_cryptoSignDetached(JNIEnv *env, jclass clazz, jbyteArray message, jbyteArray secretKey) {
    if (message == NULL || secretKey == NULL || (*env)->GetArrayLength(env, secretKey) != crypto_sign_SECRETKEYBYTES) return NULL;

    jsize msg_len = (*env)->GetArrayLength(env, message);
    unsigned char sig[crypto_sign_BYTES];
    jbyte *msg_data = (*env)->GetByteArrayElements(env, message, NULL);
    jbyte *sk_data = (*env)->GetByteArrayElements(env, secretKey, NULL);

    crypto_sign_detached(sig, NULL, (unsigned char *) msg_data, (unsigned long long) msg_len, (unsigned char *) sk_data);

    (*env)->ReleaseByteArrayElements(env, message, msg_data, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, secretKey, sk_data, JNI_ABORT);

    jbyteArray result = (*env)->NewByteArray(env, crypto_sign_BYTES);
    (*env)->SetByteArrayRegion(env, result, 0, crypto_sign_BYTES, (jbyte *) sig);

    return result;
}

JNIEXPORT jboolean JNICALL
Java_swiss_qpq_gajumobile_security_GajuNative_cryptoSignVerifyDetached(JNIEnv *env, jclass clazz, jbyteArray signature, jbyteArray message, jbyteArray publicKey) {
    if (signature == NULL || message == NULL || publicKey == NULL) return JNI_FALSE;
    if ((*env)->GetArrayLength(env, signature) != crypto_sign_BYTES) return JNI_FALSE;
    if ((*env)->GetArrayLength(env, publicKey) != crypto_sign_PUBLICKEYBYTES) return JNI_FALSE;

    jsize msg_len = (*env)->GetArrayLength(env, message);
    jbyte *sig_data = (*env)->GetByteArrayElements(env, signature, NULL);
    jbyte *msg_data = (*env)->GetByteArrayElements(env, message, NULL);
    jbyte *pk_data = (*env)->GetByteArrayElements(env, publicKey, NULL);

    int res = crypto_sign_verify_detached((unsigned char *) sig_data, (unsigned char *) msg_data, (unsigned long long) msg_len, (unsigned char *) pk_data);

    (*env)->ReleaseByteArrayElements(env, signature, sig_data, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, message, msg_data, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, publicKey, pk_data, JNI_ABORT);

    return res == 0 ? JNI_TRUE : JNI_FALSE;
}

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
    if (sodium_init() == -1) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}
