import { createHmac, timingSafeEqual } from "node:crypto";

/**
 * Confere que o aviso veio da Spedy (padrão Standard Webhooks).
 *
 * Conteúdo assinado: `{webhook-id}.{webhook-timestamp}.{corpo cru}`, HMAC-SHA256
 * com os bytes do segredo (base64, sem o prefixo `whsec_`). O cabeçalho pode
 * trazer mais de uma assinatura (`v1,<base64>` separadas por espaço) durante a
 * rotação do segredo — vale qualquer uma. O corpo tem de ser o CRU: reparsear o
 * JSON muda espaços e ordem, e a assinatura deixa de conferir.
 */

export const TOLERANCIA_DO_WEBHOOK_S = 300;

export type CabecalhosDoWebhook = {
  id: string | null;
  timestamp: string | null;
  assinatura: string | null;
};

export function assinaturaEsperada(segredo: string, id: string, timestamp: string, corpoCru: string) {
  const chave = Buffer.from(segredo.replace(/^whsec_/, ""), "base64");
  return createHmac("sha256", chave).update(`${id}.${timestamp}.${corpoCru}`).digest("base64");
}

export function webhookValido(
  segredos: string[],
  cabecalhos: CabecalhosDoWebhook,
  corpoCru: string,
  agoraEmSegundos = Math.floor(Date.now() / 1000)
): boolean {
  const { id, timestamp, assinatura } = cabecalhos;
  if (!id || !timestamp || !assinatura) return false;
  const instante = Number(timestamp);
  if (!Number.isFinite(instante) || Math.abs(agoraEmSegundos - instante) > TOLERANCIA_DO_WEBHOOK_S) return false;

  const recebidas = assinatura
    .split(" ")
    .map((parte) => {
      const [versao, valor] = parte.split(",", 2);
      return versao === "v1" && valor ? valor : null;
    })
    .filter((valor): valor is string => Boolean(valor));

  for (const segredo of segredos.filter(Boolean)) {
    const esperada = Buffer.from(assinaturaEsperada(segredo, id, timestamp, corpoCru));
    for (const recebida of recebidas) {
      const buffer = Buffer.from(recebida);
      if (buffer.length === esperada.length && timingSafeEqual(buffer, esperada)) return true;
    }
  }
  return false;
}
