import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";

/**
 * Cofre para o que não pode ficar em texto no banco.
 *
 * A chave de API de cada empresa na Spedy aparece UMA vez só, na criação, e dá
 * poder de emitir nota em nome daquele CNPJ. O CSC da NFC-e e a senha da
 * prefeitura também moram aqui, porque o PUT de configurações da Spedy zera o
 * que não vai no corpo e é preciso reenviá-los.
 *
 * AES-256-GCM com chave de 32 bytes em `FISCAL_CHAVE_COFRE` (base64). O GCM
 * autentica: um byte trocado no banco faz a leitura falhar em vez de devolver
 * lixo. Formato: `v1.<iv>.<tag>.<dados>`, cada parte em base64url.
 *
 * O servidor local e o de produção têm chaves diferentes de propósito: o que
 * um cifra o outro não abre, e cada um só usa o que é do seu ambiente.
 */

const VERSAO = "v1";

function chaveDoCofre(chaveBase64?: string): Buffer {
  const bruta = String(chaveBase64 ?? process.env.FISCAL_CHAVE_COFRE ?? "").trim();
  if (!bruta) throw new Error("O cofre das notas fiscais não está configurado neste servidor (FISCAL_CHAVE_COFRE).");
  const chave = Buffer.from(bruta, "base64");
  if (chave.length !== 32) throw new Error("FISCAL_CHAVE_COFRE precisa ter 32 bytes em base64.");
  return chave;
}

export function cofreConfigurado() {
  try {
    chaveDoCofre();
    return true;
  } catch {
    return false;
  }
}

export function cifrar(texto: string, chaveBase64?: string): string {
  const chave = chaveDoCofre(chaveBase64);
  const iv = randomBytes(12);
  const cifra = createCipheriv("aes-256-gcm", chave, iv);
  const dados = Buffer.concat([cifra.update(texto, "utf8"), cifra.final()]);
  const tag = cifra.getAuthTag();
  return [VERSAO, iv.toString("base64url"), tag.toString("base64url"), dados.toString("base64url")].join(".");
}

export function decifrar(cifrado: string, chaveBase64?: string): string {
  const partes = String(cifrado || "").split(".");
  if (partes.length !== 4 || partes[0] !== VERSAO) throw new Error("Conteúdo cifrado inválido.");
  const chave = chaveDoCofre(chaveBase64);
  const decifra = createDecipheriv("aes-256-gcm", chave, Buffer.from(partes[1], "base64url"));
  decifra.setAuthTag(Buffer.from(partes[2], "base64url"));
  return Buffer.concat([decifra.update(Buffer.from(partes[3], "base64url")), decifra.final()]).toString("utf8");
}

/** Guarda um objeto pequeno de segredos (CSC, senha da prefeitura). */
export function cifrarObjeto(objeto: Record<string, string | null | undefined>, chaveBase64?: string) {
  const limpo: Record<string, string> = {};
  for (const [chave, valor] of Object.entries(objeto)) {
    if (typeof valor === "string" && valor !== "") limpo[chave] = valor;
  }
  return cifrar(JSON.stringify(limpo), chaveBase64);
}

export function decifrarObjeto(cifrado: string | null | undefined, chaveBase64?: string): Record<string, string> {
  if (!cifrado) return {};
  const lido = JSON.parse(decifrar(cifrado, chaveBase64)) as unknown;
  if (!lido || typeof lido !== "object" || Array.isArray(lido)) return {};
  const saida: Record<string, string> = {};
  for (const [chave, valor] of Object.entries(lido as Record<string, unknown>)) {
    if (typeof valor === "string") saida[chave] = valor;
  }
  return saida;
}
