import { createHash, randomBytes, timingSafeEqual } from "node:crypto";

import { validateBrazilianDocument } from "../document-utils.ts";

/*
 * A compra rápida do produto digital: os dados que o comprador digita, a
 * conferência deles e a chave que prende a compra àquele navegador (sem senha
 * e sem sessão do portal: a chave só serve para pagar e ver ESTA compra).
 */

export type DadosDaCompra = {
  nome: string;
  email: string;
  whatsapp: string;
  documento: string;
};

export type ConferenciaDaCompra = { ok: true; dados: DadosDaCompra } | { ok: false; erros: Partial<Record<keyof DadosDaCompra | "aceite", string>> };

const DOMINIOS_COMUNS = ["gmail.com", "hotmail.com", "outlook.com", "yahoo.com.br", "yahoo.com", "icloud.com", "live.com", "uol.com.br", "bol.com.br", "terra.com.br"];

function distancia(a: string, b: string): number {
  const linha = Array.from({ length: b.length + 1 }, (_, i) => i);
  for (let i = 1; i <= a.length; i += 1) {
    let anterior = linha[0];
    linha[0] = i;
    for (let j = 1; j <= b.length; j += 1) {
      const guardado = linha[j];
      linha[j] = Math.min(linha[j] + 1, linha[j - 1] + 1, anterior + (a[i - 1] === b[j - 1] ? 0 : 1));
      anterior = guardado;
    }
  }
  return linha[b.length];
}

/* "fulano@gmial.com" → "fulano@gmail.com" (null quando parece certo). */
export function sugestaoDeEmail(email: string): string | null {
  const [usuario, dominio] = email.trim().toLowerCase().split("@");
  if (!usuario || !dominio || DOMINIOS_COMUNS.includes(dominio)) return null;
  let melhor: { dominio: string; d: number } | null = null;
  for (const comum of DOMINIOS_COMUNS) {
    const d = distancia(dominio, comum);
    if (d > 0 && d <= 2 && (!melhor || d < melhor.d)) melhor = { dominio: comum, d };
  }
  return melhor ? `${usuario}@${melhor.dominio}` : null;
}

export function emailValido(email: string): boolean {
  return /^[^\s@]{1,64}@[^\s@]{1,190}\.[a-z]{2,24}$/i.test(email.trim());
}

/**
 * Confere os dados do comprador. No produto grátis basta nome e e-mail: o
 * WhatsApp é opcional (quando vem, precisa estar certo), o CPF não é pedido e
 * não há pagamento para confirmar.
 */
export function conferirDadosDaCompra(bruto: Record<string, unknown> | null | undefined, opcoes: { gratis?: boolean } = {}): ConferenciaDaCompra {
  const texto = (valor: unknown, teto: number) => (typeof valor === "string" ? valor.replace(/\u0000/g, "").trim().slice(0, teto) : "");
  const nome = texto(bruto?.nome, 120).replace(/\s+/g, " ");
  const email = texto(bruto?.email, 254).toLowerCase();
  const whatsapp = texto(bruto?.whatsapp, 30).replace(/\D/g, "");
  const documento = texto(bruto?.documento, 30).replace(/\D/g, "");
  const erros: Partial<Record<keyof DadosDaCompra | "aceite", string>> = {};

  if (nome.length < 3 || !/\s/.test(nome)) erros.nome = "Escreva o nome completo.";
  if (!emailValido(email)) erros.email = "Confira o e-mail: é para ele que o acesso vai.";
  if ((!opcoes.gratis || whatsapp) && (whatsapp.length < 10 || whatsapp.length > 13)) erros.whatsapp = "Confira o WhatsApp com DDD.";
  if (!opcoes.gratis && !validateBrazilianDocument(documento)) erros.documento = "Confira o CPF (ou CNPJ).";
  if (!opcoes.gratis && bruto?.aceite !== true) erros.aceite = "Confirme que o acesso começa logo depois do pagamento.";

  if (Object.keys(erros).length) return { ok: false, erros };
  return { ok: true, dados: { nome, email, whatsapp, documento: opcoes.gratis ? "" : documento } };
}

/* A chave da compra: só o navegador guarda o valor; o banco guarda o resumo. */
export function novaChave(): string {
  return randomBytes(32).toString("base64url");
}

export function resumoDaChave(chave: string): string {
  return createHash("sha256").update(chave, "utf8").digest("hex");
}

export function chavesIguais(a: string, b: string): boolean {
  const x = Buffer.from(a, "utf8");
  const y = Buffer.from(b, "utf8");
  return x.length === y.length && timingSafeEqual(x, y);
}

/* O cookie da compra: um por pedido, só HTTP, sete dias. */
export function nomeDoCookieDaCompra(pedidoId: string): string {
  return `ks_compra_${pedidoId.replace(/[^0-9a-f]/gi, "").slice(0, 32)}`;
}

/* --------------------------------------------------- perguntas da encomenda */

/* O que o comprador responde na compra sob encomenda (nome no convite, idade...). */
export type CampoDaCompra = { id: string; titulo: string; tipo: "text" | "textarea" | "number"; dica: string; obrigatorio: boolean };

/* A resposta no formato dos campos personalizados do pedido (o mesmo do carrinho). */
export type RespostaDaCompra = {
  id: string;
  title: string;
  field_type: CampoDaCompra["tipo"];
  required: boolean;
  variation_signature: null;
  value: string;
};

const TETO_DA_RESPOSTA: Record<CampoDaCompra["tipo"], number> = { text: 200, textarea: 1000, number: 20 };

/* As perguntas guardadas no produto (`custom_fields_config`): só as ligadas, até 12. */
export function camposDaCompra(valor: unknown): CampoDaCompra[] {
  if (!Array.isArray(valor)) return [];
  const vistos = new Set<string>();
  const campos: CampoDaCompra[] = [];
  for (const item of valor.slice(0, 12)) {
    if (!item || typeof item !== "object") continue;
    const o = item as Record<string, unknown>;
    const id = typeof o.id === "string" ? o.id.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 40) : "";
    const titulo = typeof o.title === "string" ? o.title.replace(/\u0000/g, "").trim().slice(0, 80) : "";
    if (!id || !titulo || o.active === false || vistos.has(id)) continue;
    vistos.add(id);
    campos.push({
      id,
      titulo,
      tipo: o.field_type === "textarea" || o.field_type === "number" ? o.field_type : "text",
      dica: typeof o.placeholder === "string" ? o.placeholder.trim().slice(0, 120) : "",
      obrigatorio: o.required === true,
    });
  }
  return campos;
}

/* Confere as respostas: obrigatória preenchida, número é número, tamanho no teto. */
export function conferirRespostas(
  campos: CampoDaCompra[],
  bruto: unknown
): { ok: true; respostas: RespostaDaCompra[] } | { ok: false; erros: Record<string, string> } {
  const valores = bruto && typeof bruto === "object" && !Array.isArray(bruto) ? (bruto as Record<string, unknown>) : {};
  const erros: Record<string, string> = {};
  const respostas: RespostaDaCompra[] = [];
  for (const campo of campos) {
    const valor = typeof valores[campo.id] === "string" ? (valores[campo.id] as string).replace(/\u0000/g, "").trim() : "";
    if (!valor) {
      if (campo.obrigatorio) erros[campo.id] = "Responda para a loja fazer o seu.";
      continue;
    }
    if (valor.length > TETO_DA_RESPOSTA[campo.tipo]) {
      erros[campo.id] = `Escreva até ${TETO_DA_RESPOSTA[campo.tipo]} letras.`;
      continue;
    }
    if (campo.tipo === "number" && !/^-?\d+(?:[.,]\d+)?$/.test(valor)) {
      erros[campo.id] = "Escreva só o número.";
      continue;
    }
    respostas.push({ id: campo.id, title: campo.titulo, field_type: campo.tipo, required: campo.obrigatorio, variation_signature: null, value: valor });
  }
  return Object.keys(erros).length ? { ok: false, erros } : { ok: true, respostas };
}
