import { createCipheriv, createDecipheriv, randomBytes, timingSafeEqual } from 'crypto';
import { InternalServerErrorException } from '@nestjs/common';

/**
 * Cifrado simétrico de credenciales guardadas en base.
 *
 * Mismo algoritmo y formato (`iv_hex:dato_hex`) que ya usan `ai-config`,
 * `novasis-pay-config`, `bancard-config` y `marangatu`, que lo tienen copiado
 * cada uno con su clave de entorno. Esto no los refactoriza —son módulos en
 * producción y no es el momento— pero evita una quinta copia: la clave entra por
 * parámetro, así que cada módulo sigue usando la suya.
 */

const ALGORITMO = 'aes-256-cbc';
const LARGO_IV = 16;
const LARGO_CLAVE = 32;

function aBuffer(clave: string, nombreEnv: string): Buffer {
  if (clave.length !== LARGO_CLAVE) {
    // El mensaje nombra la variable: el que despliega necesita saber cuál.
    throw new InternalServerErrorException(
      `${nombreEnv} debe tener exactamente ${LARGO_CLAVE} caracteres`,
    );
  }
  return Buffer.from(clave, 'utf8');
}

export function cifrar(texto: string, clave: string, nombreEnv: string): string {
  const iv = randomBytes(LARGO_IV);
  const cipher = createCipheriv(ALGORITMO, aBuffer(clave, nombreEnv), iv);
  const cifrado = Buffer.concat([cipher.update(texto, 'utf8'), cipher.final()]);
  return `${iv.toString('hex')}:${cifrado.toString('hex')}`;
}

export function descifrar(cifrado: string, clave: string, nombreEnv: string): string {
  const [ivHex, datoHex] = cifrado.split(':');
  if (!ivHex || !datoHex) {
    throw new InternalServerErrorException('El dato cifrado no tiene el formato esperado');
  }
  const decipher = createDecipheriv(
    ALGORITMO,
    aBuffer(clave, nombreEnv),
    Buffer.from(ivHex, 'hex'),
  );
  return Buffer.concat([decipher.update(Buffer.from(datoHex, 'hex')), decipher.final()]).toString('utf8');
}

/**
 * Para devolver una credencial por API sin exponerla. Un secreto corto se tapa
 * entero: mostrar 8 de 12 caracteres es casi mostrarlo.
 */
export function enmascarar(valor: string | null | undefined): string | null {
  if (!valor) return null;
  if (valor.length <= 12) return '••••';
  return `${valor.slice(0, 6)}…${valor.slice(-4)}`;
}

/**
 * Comparación de firmas en tiempo constante. `===` filtra por cuánto tarda en
 * fallar cuántos caracteres coincidían, que es como se adivina un HMAC byte a
 * byte. `timingSafeEqual` exige el mismo largo, así que eso se chequea antes.
 */
export function firmaCoincide(a: string, b: string): boolean {
  const ba = Buffer.from(a, 'utf8');
  const bb = Buffer.from(b, 'utf8');
  if (ba.length !== bb.length) return false;
  return timingSafeEqual(ba, bb);
}
