/**
 * File: encryptionFunctions.ts
 * Purpose: This file contains functions for encrypting and decrypting data using AES encryption.
 *          It uses the `crypto-js` library to perform the encryption and decryption operations.
*/

import CryptoJS from 'crypto-js';

// Retrieve the encryption key from environment variables (ensure this is set securely)
const ENCRYPTION_KEY = process.env.NEXT_PUBLIC_ENCRYPTION_KEY || ''; // Replace with a secure key
const AES_KEY = ENCRYPTION_KEY ? CryptoJS.enc.Hex.parse(ENCRYPTION_KEY) : null; // Convert to Hex
const HMAC_SECRET = process.env.NEXT_PUBLIC_HMAC_SECRET; // Replace with a secure secret

/**
 * Encrypts a given string using AES encryption.
 * @param data - The plain text data to encrypt.
 * @returns The encrypted data as a string.
 */
export const encryptData = (data: string): string => {
    return encodeURIComponent(CryptoJS.AES.encrypt(data, ENCRYPTION_KEY).toString());
};

/**
 * Decrypts a given AES-encrypted string back into plain text.
 * @param cipherText - The encrypted string to decrypt.
 * @returns The decrypted plain text data as a string, or the original input if empty.
 */
export const decryptData: any = (cipherText: string): string => {
    if (!cipherText) {
        return cipherText; // Return the input as-is if no cipher text is provided
    }
    const bytes = CryptoJS.AES.decrypt(cipherText, ENCRYPTION_KEY!); // Decrypt the cipher text
    return bytes.toString(CryptoJS.enc.Utf8); // Convert the bytes back to a UTF-8 string
};

/**
 * Encrypts a given payload using AES encryption and HMAC.
 * @param payload - The payload to encrypt.
 * @returns The encrypted data as a string.
 */
export function encryptWithHMAC(payload: any) {
    const iv = CryptoJS.lib.WordArray.random(16);
    const encrypted = CryptoJS.AES.encrypt(JSON.stringify(payload), AES_KEY, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    });
    const ivBase64 = CryptoJS.enc.Base64.stringify(iv);
    const encryptedBase64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64)
    // Generate HMAC of IV + ciphertext
    const hmac = CryptoJS.HmacSHA256(ivBase64 + encryptedBase64, HMAC_SECRET).toString();
    return {
        iv: ivBase64,
        data: encryptedBase64,
        hmac: hmac,
    };
}

/**
 * Decrypts an encrypted payload using AES encryption and HMAC.
 * @param encryptedData - The encrypted data to decrypt.
 * @returns The decrypted payload as an object.
 */
export function decryptWithHMAC(encryptedData: any) {
    const { iv, data, hmac } = encryptedData;
    // Verify HMAC
    const hmacToVerify = CryptoJS.HmacSHA256(iv + data, HMAC_SECRET).toString();
    if (hmac !== hmacToVerify) {
        throw new Error("HMAC verification failed. Data may be tampered with.");
    }
    const decrypted = CryptoJS.AES.decrypt(data, AES_KEY, {
        iv: CryptoJS.enc.Base64.parse(iv),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    });
    // Convert the decrypted bytes back to a UTF-8 string
    const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
    return JSON.parse(decryptedText);
}


/**
 * Decrypts an encrypted payload using AES encryption and HMAC.
 * This function is used by the server to decrypt data sent from the client.
 * @param encryptedData - The encrypted data to decrypt.
 * @returns The decrypted payload as an object.
 * @throws If the HMAC verification fails, or if the encrypted data is missing.
 */
export function serverDecrypWithHMAC(encryptedData: { iv: string; data: string; hmac: string }) {
    const { iv, data, hmac } = encryptedData;

    if (!iv || !data || !hmac) {
        throw new Error("Missing iv, data, or hmac in encryptedData");
    }

    if (!HMAC_SECRET) {
        throw new Error("HMAC_SECRET is undefined");
    }

    // Calculate HMAC
    const hmacToVerify = CryptoJS.HmacSHA256(iv + data, HMAC_SECRET).toString();
    if (hmac !== hmacToVerify) {
        throw new Error("HMAC verification failed. Data may be tampered with.");
    }

    // Decrypt AES
    const decrypted = CryptoJS.AES.decrypt(data, AES_KEY, {
        iv: CryptoJS.enc.Base64.parse(iv),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    });

    const decryptedText = decrypted?.toString(CryptoJS.enc.Utf8);

    return JSON.parse(decryptedText);
}

/**
 * Encrypts a given payload using AES encryption and HMAC.
 * This function is used by the server to encrypt data sent to the client.
 * @param payload - The payload to encrypt.
 * @returns An object containing the encrypted data, the IV, and the HMAC.
 * @throws If AES_KEY or HMAC_SECRET is undefined.
 */
export function serverEncryptWithHMAC(payload: object) {
    if (!AES_KEY || !HMAC_SECRET) {
        throw new Error('AES_KEY or HMAC_SECRET is undefined');
    }

    // Convert payload to JSON string
    const plainText = JSON.stringify(payload);

    // Generate random 128-bit IV
    const iv = CryptoJS.lib.WordArray.random(16); // 16 bytes = 128 bits

    // Encrypt using AES-CBC
    const encrypted = CryptoJS.AES.encrypt(plainText, AES_KEY, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
    });

    // Get Base64 representations
    const encryptedData = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
    const ivBase64 = iv.toString(CryptoJS.enc.Base64);

    // Create HMAC for iv + encryptedData
    const hmac = CryptoJS.HmacSHA256(ivBase64 + encryptedData, HMAC_SECRET).toString();

    return {
        iv: ivBase64,
        data: encryptedData,
        hmac,
    };
}