import crypto from "crypto";

/**
 * O link publico do pedido de compra.
 *
 * O que havia antes: `id.assinatura`, onde a assinatura era funcao pura do id
 * da compra. Isso significa tres coisas ruins ao mesmo tempo. O link nunca
 * expirava. Nao existia jeito de desativar um link vazado, porque nao havia
 * nada para trocar: o mesmo id sempre gerava o mesmo endereco. E o segredo
 * tinha um valor de reserva escrito neste arquivo, entao um servidor sem a
 * variavel de ambiente assinava com uma senha que esta no repositorio.
 *
 * Agora o token e `id.expiraEm.versao.assinatura`, e a assinatura cobre os
 * tres campos. A validade viaja dentro do token (nao precisa ir ao banco para
 * saber que venceu) e a versao e conferida contra a coluna
 * `supplier_purchase_orders.public_link_version`: subir a versao invalida na
 * hora todo link ja enviado daquela compra, que e o botao "gerar novo link".
 *
 * Links no formato antigo deixam de abrir. E o preco de ter validade e
 * revogacao, e a tela tem o botao para gerar o novo.
 */

export const DIAS_DE_VALIDADE_DO_LINK = 90;

function getSigningSecret() {
  const segredo = (process.env.SUPABASE_SERVICE_ROLE_KEY || "").trim();
  if (!segredo) {
    /* Sem valor de reserva de proposito. Um segredo escrito no codigo nao e
       segredo: qualquer pessoa com o id de uma compra forjaria o link. */
    throw new Error("SUPABASE_SERVICE_ROLE_KEY ausente: nao da para assinar o link do pedido de compra.");
  }
  return segredo;
}

function assinar(payload: string) {
  return crypto.createHmac("sha256", getSigningSecret()).update(payload).digest("base64url").slice(0, 22);
}

/** Comparacao de tempo constante, para a assinatura nao vazar por medida de tempo. */
function assinaturasIguais(recebida: string, esperada: string) {
  const a = Buffer.from(recebida);
  const b = Buffer.from(esperada);
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

function removeTrailingSlash(value: string) {
  return value.replace(/\/$/, "");
}

export function createSupplierPurchaseOrderPublicToken(input: {
  purchaseOrderId: string;
  /* A versao gravada na compra. Compras antigas, sem a coluna, valem 1. */
  linkVersion?: number | null;
  diasDeValidade?: number;
}) {
  const versao = Math.max(1, Math.trunc(Number(input.linkVersion ?? 1)) || 1);
  const dias = Math.max(1, Math.trunc(input.diasDeValidade ?? DIAS_DE_VALIDADE_DO_LINK));
  /* Em segundos e base36 para o endereco nao ficar comprido. */
  const expiraEm = Math.floor(Date.now() / 1000) + dias * 24 * 60 * 60;

  const corpo = `${input.purchaseOrderId}.${expiraEm.toString(36)}.${versao.toString(36)}`;
  return `${corpo}.${assinar(corpo)}`;
}

export type PedidoDoToken = {
  purchaseOrderId: string;
  /* A versao que o link carrega. Quem chama compara com a gravada na compra. */
  linkVersion: number;
  expiraEm: Date;
};

export type RecusaDoToken = "formato" | "assinatura" | "vencido";

export function verifySupplierPurchaseOrderPublicToken(
  token: string
): { ok: true; pedido: PedidoDoToken } | { ok: false; motivo: RecusaDoToken } {
  const partes = String(token || "").split(".");
  /* Exatamente quatro. Antes o split ignorava o que viesse depois, entao
     `token.qualquercoisa` abria a mesma pagina por N enderecos diferentes. */
  if (partes.length !== 4) return { ok: false, motivo: "formato" };

  const [purchaseOrderId, expiraBase36, versaoBase36, assinatura] = partes;
  if (!purchaseOrderId || !expiraBase36 || !versaoBase36 || !assinatura) {
    return { ok: false, motivo: "formato" };
  }

  const corpo = `${purchaseOrderId}.${expiraBase36}.${versaoBase36}`;
  if (!assinaturasIguais(assinatura, assinar(corpo))) {
    return { ok: false, motivo: "assinatura" };
  }

  const expiraEmSegundos = Number.parseInt(expiraBase36, 36);
  const linkVersion = Number.parseInt(versaoBase36, 36);
  if (!Number.isFinite(expiraEmSegundos) || !Number.isFinite(linkVersion)) {
    return { ok: false, motivo: "formato" };
  }

  if (expiraEmSegundos * 1000 <= Date.now()) {
    return { ok: false, motivo: "vencido" };
  }

  return {
    ok: true,
    pedido: {
      purchaseOrderId,
      linkVersion,
      expiraEm: new Date(expiraEmSegundos * 1000),
    },
  };
}

export function buildSupplierPurchaseOrderPublicUrl(origin: string, token: string, options?: { print?: boolean }) {
  const base = `${removeTrailingSlash(origin)}/pedido-compra/${encodeURIComponent(token)}`;
  return options?.print ? `${base}?print=1` : base;
}
