/*
 * 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
 */

package swiss.qpq.gajumobile.security;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.UUID;
import javax.crypto.SecretKey;
import swiss.qpq.gajumaru.core.encoding.Mnemonic;
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
import swiss.qpq.gajumaru.core.tools.TransactionService;
import swiss.qpq.gajumobile.data.models.Account;

/**
 * AccountAirlock provides a secure Java-based bridge for identity operations.
 * It ensures that sensitive material only exists in local stack-allocated
 * buffers and is wiped immediately after use.
 */
public final class AccountAirlock {

    private static void checkNative() {
        if (GajuNative.INSTANCE.getLoadError() != null) {
            throw new RuntimeException("Native library (gaju-native) not available: " + GajuNative.INSTANCE.getLoadError());
        }
    }

    private static void safeMemzero(byte[] array) {
        if (array != null && GajuNative.INSTANCE.getLoadError() == null) {
            GajuNative.memzero(array);
        }
    }

    /**
     * Returns the 4096-word Gajumaru dictionary.
     */
    public static String[] getWordList() {
        return Mnemonic.WORDS;
    }

    /**
     * Generates a new random mnemonic phrase.
     * 
     * @return The 23-word mnemonic phrase as a byte array of UTF-8 words.
     */
    public static byte[][] generateMnemonic() {
        byte[] entropy = new byte[32];
        new SecureRandom().nextBytes(entropy);
        try {
            return Mnemonic.encode(entropy);
        } finally {
            CryptoUtils.wipe(entropy);
        }
    }

    /**
     * Creates a new Account object from a mnemonic phrase.
     */
    public static Account createAccount(
            String label,
            byte[][] phrase,
            SecretKey masterKey
    ) throws Exception {
        
        byte[] seed = null;
        
        try {
            // 1. Decode mnemonic to seed
            seed = Mnemonic.decode(phrase);

            // 2. Generate Public Key using Libsodium
            checkNative();
            byte[] keypair = GajuNative.cryptoSignSeedKeypair(seed);
            if (keypair == null) {
                throw new RuntimeException("Native keypair generation failed (returned null).");
            }
            byte[] publicKey = new byte[32];
            System.arraycopy(keypair, 0, publicKey, 0, 32);
            // keypair contains sk too, but we only need pk here for the Account model
            safeMemzero(keypair);

            // 3. Generate random Data Encryption Key (DEK)
            // 4. Encrypt Private Key (seed) with DEK
            // 5. Encrypt DEK with Master Key
            EncryptedEnvelope envelope = EncryptionService.encrypt(masterKey, seed);

            String id = UUID.randomUUID().toString();
            return new Account(id, label, publicKey, envelope, null);

        } finally {
            if (seed != null) safeMemzero(seed);
        }
    }

    /**
     * Decrypts an account's private key and signs a message.
     */
    public static byte[] signMessage(
            EncryptedEnvelope envelope,
            SecretKey masterKey,
            String message
    ) throws Exception {
        byte[] seed = derivePrivateKey(envelope, masterKey);
        try {
            checkNative();
            byte[] keypair = GajuNative.cryptoSignSeedKeypair(seed);
            if (keypair == null) throw new RuntimeException("Native keypair generation failed.");
            byte[] secretKey = new byte[64];
            System.arraycopy(keypair, 32, secretKey, 0, 64);
            
            byte[] sig = TransactionService.signMessage(message, secretKey);
            
            safeMemzero(keypair);
            safeMemzero(secretKey);
            return sig;
        } finally {
            safeMemzero(seed);
        }
    }

    /**
     * Decrypts an account's private key and signs binary data.
     */
    public static byte[] signBinary(
            EncryptedEnvelope envelope,
            SecretKey masterKey,
            byte[] data
    ) throws Exception {
        byte[] seed = derivePrivateKey(envelope, masterKey);
        try {
            checkNative();
            byte[] keypair = GajuNative.cryptoSignSeedKeypair(seed);
            if (keypair == null) throw new RuntimeException("Native keypair generation failed.");
            byte[] secretKey = new byte[64];
            System.arraycopy(keypair, 32, secretKey, 0, 64);
            
            byte[] sig = TransactionService.signBinary(data, secretKey);
            
            safeMemzero(keypair);
            safeMemzero(secretKey);
            return sig;
        } finally {
            safeMemzero(seed);
        }
    }

    /**
     * Decrypts an account's private key and signs a transaction.
     */
    public static String signTx(
            EncryptedEnvelope envelope,
            SecretKey masterKey,
            byte[] txData,
            String networkId
    ) throws Exception {
        byte[] seed = derivePrivateKey(envelope, masterKey);
        try {
            checkNative();
            byte[] keypair = GajuNative.cryptoSignSeedKeypair(seed);
            if (keypair == null) throw new RuntimeException("Native keypair generation failed.");
            byte[] secretKey = new byte[64];
            System.arraycopy(keypair, 32, secretKey, 0, 64);
            
            String signedTx = TransactionService.signTx(txData, networkId, secretKey);
            
            safeMemzero(keypair);
            safeMemzero(secretKey);
            return signedTx;
        } finally {
            safeMemzero(seed);
        }
    }

    /**
     * Decrypts an account's private key (seed).
     * WARNING: Result must be wiped with GajuNative.memzero() immediately after use.
     */
    public static byte[] derivePrivateKey(
            EncryptedEnvelope envelope,
            SecretKey masterKey
    ) throws Exception {
        return EncryptionService.decrypt(masterKey, envelope);
    }

    /**
     * Decrypts an account's private key (seed) and derives the mnemonic.
     */
    public static byte[][] deriveMnemonic(
            EncryptedEnvelope envelope,
            SecretKey masterKey
    ) throws Exception {
        byte[] plaintextPrivateKey = derivePrivateKey(envelope, masterKey);
        try {
            return Mnemonic.encode(plaintextPrivateKey);
        } finally {
            safeMemzero(plaintextPrivateKey);
        }
    }

    /**
     * Utility to wipe a phrase returned by the airlock.
     */
    public static void wipePhrase(byte[][] phrase) {
        if (phrase != null) {
            for (byte[] word : phrase) {
                CryptoUtils.wipe(word);
            }
        }
    }
}
