/*
 * Copyright (c) 2022.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import {box, BoxKeyPair, randomBytes, secretbox} from "tweetnacl";
import {decodeBase64, decodeUTF8, encodeBase64, encodeUTF8} from 'tweetnacl-util';

/*
Public Key Encryption
 */
export function createPublicEncryptionNonce() : Uint8Array {
    return randomBytes(box.nonceLength);
}

export function createPublicEncryptionKeyPair() : Record<'privateKey' | 'publicKey', string> {
    const keyPair : BoxKeyPair = box.keyPair();

    return {
        privateKey: encodeBase64(keyPair.secretKey),
        publicKey: encodeBase64(keyPair.publicKey)
    }
}

export function createPublicEncryptionKeyPairFromPrivateKey(privateKey: string) : Record<'privateKey' | 'publicKey', string> {
    const privateKeyUint8 : Uint8Array = decodeUTF8(privateKey);
    const keyPair : BoxKeyPair = box.keyPair.fromSecretKey(privateKeyUint8);

    return {
        privateKey: encodeBase64(keyPair.secretKey),
        publicKey: encodeBase64(keyPair.publicKey)
    }
}

export function encryptPublicEncryptionMessage(message: string, publicKey: string, privateKey: string) : string {
    const nonce = createPublicEncryptionNonce();
    const messageUint8 : Uint8Array = decodeUTF8(message);

    const publicKeyUint8 : Uint8Array = decodeBase64(publicKey);
    const privateKeyKeyUint8 : Uint8Array = decodeBase64(privateKey);

    const encrypted = box(messageUint8, nonce, publicKeyUint8, privateKeyKeyUint8);

    const combined : Uint8Array = new Uint8Array(nonce.length + encrypted.length);
    combined.set(nonce);
    combined.set(encrypted, nonce.length);

    return encodeBase64(combined);
}

export function decryptPublicEncryptionMessage(messageWithNonce: string, publicKey: string, privateKey: string) : string {
    const messageWithNonceAsUint8Array = decodeBase64(messageWithNonce);

    const nonce = messageWithNonceAsUint8Array.slice(0, box.nonceLength);
    const message = messageWithNonceAsUint8Array.slice(
        box.nonceLength,
        messageWithNonce.length
    );

    const publicKeyUint8 : Uint8Array = decodeBase64(publicKey);
    const privateKeyKeyUint8 : Uint8Array = decodeBase64(privateKey);

    const decrypted : Uint8Array | null = box.open(message, nonce, publicKeyUint8, privateKeyKeyUint8);
    if(!decrypted) {
        throw new Error('Could not decrypt message...');
    }

    return encodeUTF8(decrypted);
}

/*
Secret Key Encryption
 */

export function createSymmetricEncryptionNonce() : Uint8Array {
    return randomBytes(secretbox.nonceLength);
}

export function createSymmetricEncryptionKey() : string {
    return encodeBase64(randomBytes(secretbox.keyLength))
}

export function encryptMessageSymmetric(message: string, secretKey: string) {
    const keyUint8Array = decodeBase64(secretKey);

    const nonce = createSymmetricEncryptionNonce();
    const messageUint8 = decodeUTF8(message);
    const out = secretbox(messageUint8, nonce, keyUint8Array);

    const combined = new Uint8Array(nonce.length + out.length);
    combined.set(nonce);
    combined.set(out, nonce.length);

    return encodeBase64(combined);
}

export function decryptMessageSymmetric(messageWithNonce: string, secretKey: string) {
    const keyUint8Array = decodeBase64(secretKey);
    const messageWithNonceAsUint8Array = decodeBase64(messageWithNonce);
    const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);
    const message = messageWithNonceAsUint8Array.slice(
        secretbox.nonceLength,
        messageWithNonce.length
    );

    const decrypted = secretbox.open(message, nonce, keyUint8Array);

    if (!decrypted) {
        throw new Error("Could not decrypt message");
    }

    return encodeUTF8(decrypted);
}
