import CryptoJS from "crypto-js";
import * as env from "../config/env.config";

/**
 * Módulo de cifrado/descifrado AES-CBC compatible con implementaciones .NET.
 *
 * Algoritmo utilizado: AES-256-CBC con padding PKCS7.
 * Derivación de clave e IV: PBKDF2 con 1000 iteraciones sobre `ENCRYPTION_KEY`.
 * Codificación de texto: UTF-16 LE (equivalente a `Encoding.Unicode` en C#),
 * lo que garantiza interoperabilidad con cifrado realizado desde el backend .NET.
 */

// Salt fijo usado para la derivación de clave (PBKDF2).
// Debe mantenerse consistente entre cifrado y descifrado.
const salt = CryptoJS.lib.WordArray.create([
  0x0, 0x1, 0x2, 0x1c, 0x1d, 0x1e, 0x3, 0x4, 0x5, 0xf, 0x20, 0x21, 0xad, 0xaf,
  0xa4,
]); // Salt como WordArray

/**
 * Cifra un bloque de bytes usando AES-256-CBC con padding PKCS7.
 *
 * @param clearData - Datos en claro como WordArray (UTF-16 LE)
 * @param key - Clave AES de 256 bits derivada con PBKDF2
 * @param iv - Vector de inicialización de 128 bits derivado con PBKDF2
 * @returns Datos cifrados como WordArray en formato Base64 parseado
 */
function encryptBytes(
  clearData: CryptoJS.lib.WordArray,
  key: CryptoJS.lib.WordArray,
  iv: CryptoJS.lib.WordArray,
): CryptoJS.lib.WordArray {
  const encrypted = CryptoJS.AES.encrypt(clearData, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
  });
  return CryptoJS.enc.Base64.parse(encrypted.toString()); // Retorna los bytes en formato WordArray
}

/**
 * Cifra un string y retorna el resultado en Base64.
 *
 * El string se convierte a bytes usando UTF-16 LE para ser compatible
 * con `Encoding.Unicode` de C#. La clave y el IV se derivan con PBKDF2
 * usando `ENCRYPTION_KEY` del entorno y el salt estático definido arriba.
 *
 * @param data - Texto plano a cifrar
 * @returns String cifrado codificado en Base64
 */
export function encrypt(data: string): string {
  const clearBytes = CryptoJS.enc.Utf16LE.parse(data); // Usa UTF16LE para coincidir con Encoding.Unicode de C#
  const key = CryptoJS.PBKDF2(env.ENCRYPTION_KEY, salt, {
    keySize: 256 / 32,
    iterations: 1000,
  }); // PBKDF2 para derivar la clave de 256 bits
  const iv = CryptoJS.PBKDF2(env.ENCRYPTION_KEY, salt, {
    keySize: 128 / 32,
    iterations: 1000,
  }); // PBKDF2 para derivar el IV de 128 bits
  const encryptedData = encryptBytes(clearBytes, key, iv);
  return CryptoJS.enc.Base64.stringify(encryptedData);
}

/**
 * Descifra un string en Base64 previamente cifrado con `encrypt`.
 *
 * Deriva la misma clave e IV usando PBKDF2 con el mismo salt,
 * aplica AES-256-CBC y decodifica el resultado desde UTF-16 LE a string.
 *
 * @param encryptedData - Texto cifrado en Base64
 * @returns Texto plano descifrado
 */
export function decrypt(encryptedData: string): string {
  const key = CryptoJS.PBKDF2(env.ENCRYPTION_KEY, salt, {
    keySize: 256 / 32,
    iterations: 1000,
  });
  const iv = CryptoJS.PBKDF2(env.ENCRYPTION_KEY, salt, {
    keySize: 128 / 32,
    iterations: 1000,
  });
  const decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
  });
  return CryptoJS.enc.Utf16LE.stringify(decrypted);
}
