import {
  createCipheriv,
  createDecipheriv,
  randomBytes,
  timingSafeEqual,
} from "node:crypto";
import { env } from "../config/env.js";

/**
 * Camada de criptografia simétrica AES-256-GCM para os campos sensíveis.
 *
 * Tudo que é segredo ou dado pessoal (api_secret das exchanges, senha de
 * proxy, token do bot Telegram, CPF/telefone/PIX de clientes) é guardado
 * CRIPTOGRAFADO em colunas `VARBINARY` do banco. Aqui mora a chave mestra
 * e o formato do ciphertext.
 *
 * Formato do payload (gravado direto no VARBINARY):
 *
 *     | nonce (12 bytes) | authTag (16 bytes) | ciphertext (N bytes) |
 *
 * A chave mestra vem de `MASTER_KEY` (env), NUNCA do banco, NUNCA do Git.
 *
 * Regras (CLAUDE.md): nunca logar plaintext nem a chave; nunca devolver o
 * secret pro frontend (use `lastFour`).
 */

const ALGO = "aes-256-gcm";
const NONCE_LEN = 12; // 96 bits, recomendado para GCM
const TAG_LEN = 16; // 128 bits

// Chave de 32 bytes derivada do hex de MASTER_KEY. O schema do env já
// garante 64 chars hex; ainda assim conferimos o tamanho em bytes.
const KEY: Buffer = (() => {
  const key = Buffer.from(env.MASTER_KEY, "hex");
  if (key.length !== 32) {
    throw new Error("MASTER_KEY deve representar exatamente 32 bytes (256 bits).");
  }
  return key;
})();

/** Criptografa um texto e devolve o Buffer `nonce|tag|ciphertext`. */
export function encrypt(plaintext: string): Buffer {
  const nonce = randomBytes(NONCE_LEN);
  const cipher = createCipheriv(ALGO, KEY, nonce);
  const ciphertext = Buffer.concat([
    cipher.update(plaintext, "utf8"),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();
  return Buffer.concat([nonce, tag, ciphertext]);
}

/**
 * Descriptografa um Buffer `nonce|tag|ciphertext` e devolve o texto.
 * Lança se o payload estiver malformado ou a tag não bater (dado
 * adulterado ou chave errada).
 */
export function decrypt(payload: Buffer): string {
  if (payload.length < NONCE_LEN + TAG_LEN) {
    throw new Error("Ciphertext inválido: payload curto demais.");
  }
  const nonce = payload.subarray(0, NONCE_LEN);
  const tag = payload.subarray(NONCE_LEN, NONCE_LEN + TAG_LEN);
  const ciphertext = payload.subarray(NONCE_LEN + TAG_LEN);

  const decipher = createDecipheriv(ALGO, KEY, nonce);
  decipher.setAuthTag(tag);
  try {
    return Buffer.concat([
      decipher.update(ciphertext),
      decipher.final(),
    ]).toString("utf8");
  } catch {
    // Mensagem genérica: não vaza nada sobre a chave nem o conteúdo.
    throw new Error("Falha ao descriptografar: dado adulterado ou chave inválida.");
  }
}

/** Versão tolerante a null — útil para colunas `*_enc` opcionais. */
export function encryptNullable(plaintext: string | null | undefined): Buffer | null {
  if (plaintext === null || plaintext === undefined || plaintext === "") {
    return null;
  }
  return encrypt(plaintext);
}

/** Versão tolerante a null — útil ao ler colunas `*_enc` opcionais. */
export function decryptNullable(payload: Buffer | null | undefined): string | null {
  if (payload === null || payload === undefined) {
    return null;
  }
  return decrypt(payload);
}

/**
 * Últimos 4 caracteres de um segredo, para exibir no frontend sem nunca
 * revelar o valor (ex: "••••••3F9A"). Strings com 4 ou menos chars são
 * mascaradas por inteiro.
 */
export function lastFour(secret: string): string {
  if (secret.length <= 4) return "••••";
  return `••••${secret.slice(-4)}`;
}

/**
 * Comparação de segredos em tempo constante (evita timing attacks).
 * Útil para confrontar tokens/credenciais sem vazar pelo tempo de resposta.
 */
export function safeEqual(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);
}
