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

/**
 * O Mercado Pago DA KTALOGU, que vende os pacotes de notas (Checkout Pro).
 *
 * Não confundir com `lib/mercado-pago.ts`: aquele é o Mercado Pago de cada
 * gráfica, que recebe das vendas dela. Este usa a credencial da Ktalogu, fica
 * em variável de ambiente e nunca passa pelo banco.
 *
 * Fluxo: cria a preferência com `external_reference` = id da compra, o
 * cliente paga na página do Mercado Pago, e a compra é conferida por três
 * caminhos que dão no mesmo lugar (a função do banco é idempotente): o aviso
 * do Mercado Pago, a volta do cliente para o Ktalogu e a rotina que confere
 * compras pendentes.
 */

const API = "https://api.mercadopago.com";

export class ErroDoMercadoPago extends Error {
  status: number;
  constructor(mensagem: string, status: number) {
    super(mensagem);
    this.name = "ErroDoMercadoPago";
    this.status = status;
  }
}

async function chamar<T>(token: string, caminho: string, init: RequestInit & { idempotencia?: string } = {}): Promise<T> {
  const controle = new AbortController();
  const relogio = setTimeout(() => controle.abort(), 20000);
  try {
    const headers: Record<string, string> = {
      Authorization: `Bearer ${token}`,
      Accept: "application/json",
    };
    if (init.body) headers["Content-Type"] = "application/json";
    if (init.idempotencia) headers["X-Idempotency-Key"] = init.idempotencia;
    const resposta = await fetch(`${API}${caminho}`, { ...init, headers, signal: controle.signal, cache: "no-store" });
    const textoCru = await resposta.text();
    let corpo: unknown = null;
    try {
      corpo = textoCru ? JSON.parse(textoCru) : null;
    } catch {
      corpo = textoCru;
    }
    if (!resposta.ok) {
      const registro = corpo && typeof corpo === "object" ? (corpo as Record<string, unknown>) : {};
      const mensagem = String(registro.message || registro.error || `Mercado Pago respondeu ${resposta.status}.`);
      throw new ErroDoMercadoPago(mensagem, resposta.status);
    }
    return corpo as T;
  } catch (erro) {
    if (erro instanceof ErroDoMercadoPago) throw erro;
    throw new ErroDoMercadoPago("Não foi possível falar com o Mercado Pago agora.", 0);
  } finally {
    clearTimeout(relogio);
  }
}

export type PreferenciaCriada = { id: string; init_point?: string | null; sandbox_init_point?: string | null };

/**
 * O Mercado Pago só aceita `auto_return` (voltar sozinho ao site depois de
 * pagar) quando a URL de volta é pública. Com `localhost` ou endereço de rede
 * interna ele recusa a preferência inteira — "auto_return invalid.
 * back_url.success must be defined" — e a compra nem chega a abrir. Fora do
 * ar público, a volta fica por conta do botão "Voltar ao site" da própria
 * página de pagamento.
 */
export function voltaAutomaticaPermitida(url: string) {
  let alvo: URL;
  try {
    alvo = new URL(url);
  } catch {
    return false;
  }
  if (alvo.protocol !== "https:") return false;
  const nome = alvo.hostname.toLowerCase();
  if (nome === "localhost" || nome.endsWith(".localhost") || nome.endsWith(".local")) return false;
  if (nome === "[::1]" || nome === "::1" || /^\d{1,3}(\.\d{1,3}){3}$/.test(nome)) return false;
  return true;
}

export async function criarPreferenciaDoPacote(input: {
  token: string;
  compraId: string;
  pacote: { nome: string; quantidade: number; preco: number };
  comprador: { email: string | null; nome: string | null };
  urlDeVolta: string;
  urlDoAviso: string | null;
  /* Outro produto da Ktalogu no mesmo checkout (o KtaloguLive): o que aparece no Mercado Pago. */
  item?: { id: string; title: string; description: string };
}) {
  const corpo: Record<string, unknown> = {
    external_reference: input.compraId,
    items: [
      {
        id: input.item?.id ?? `notas-${input.pacote.quantidade}`,
        title: input.item?.title ?? `Ktalogu — ${input.pacote.nome}`,
        description: input.item?.description ?? `${input.pacote.quantidade} notas fiscais para usar neste mês`,
        category_id: "services",
        quantity: 1,
        currency_id: "BRL",
        unit_price: Math.round(input.pacote.preco * 100) / 100,
      },
    ],
    back_urls: {
      success: input.urlDeVolta,
      pending: input.urlDeVolta,
      failure: input.urlDeVolta,
    },
    statement_descriptor: "KTALOGU",
    metadata: { compra_id: input.compraId },
    /* A preferência vale até o fim do dia seguinte: o pacote é do mês e um
       link velho não pode ser pago no mês que vem com o preço de hoje. */
    expires: true,
    expiration_date_to: new Date(Date.now() + 36 * 60 * 60 * 1000).toISOString(),
  };
  if (voltaAutomaticaPermitida(input.urlDeVolta)) corpo.auto_return = "approved";
  if (input.urlDoAviso) corpo.notification_url = input.urlDoAviso;
  if (input.comprador.email) {
    corpo.payer = { email: input.comprador.email, ...(input.comprador.nome ? { name: input.comprador.nome } : {}) };
  }
  return chamar<PreferenciaCriada>(input.token, "/checkout/preferences", {
    method: "POST",
    body: JSON.stringify(corpo),
    idempotencia: `preferencia-${input.compraId}`,
  });
}

export type PagamentoDoMercadoPago = {
  id: number | string;
  status: string;
  status_detail?: string | null;
  payment_type_id?: string | null;
  transaction_amount?: number | null;
  date_approved?: string | null;
  date_created?: string | null;
  external_reference?: string | null;
};

export function obterPagamento(token: string, id: string) {
  return chamar<PagamentoDoMercadoPago>(token, `/v1/payments/${encodeURIComponent(id)}`);
}

export async function pagamentosDaCompra(token: string, compraId: string) {
  const resposta = await chamar<{ results?: PagamentoDoMercadoPago[] }>(
    token,
    `/v1/payments/search?external_reference=${encodeURIComponent(compraId)}&sort=date_created&criteria=desc&limit=20`
  );
  return resposta?.results ?? [];
}

/**
 * Entre vários pagamentos da mesma compra (cartão recusado e depois Pix
 * aprovado, por exemplo), o que manda: aprovado > em análise > o mais recente.
 */
export function pagamentoQueManda(pagamentos: PagamentoDoMercadoPago[]): PagamentoDoMercadoPago | null {
  if (!pagamentos.length) return null;
  const peso = (status: string) => {
    if (status === "refunded" || status === "charged_back") return 5;
    if (status === "approved") return 4;
    if (status === "in_mediation") return 3;
    if (status === "authorized" || status === "in_process" || status === "pending") return 2;
    return 1;
  };
  return [...pagamentos].sort((a, b) => {
    const diferenca = peso(b.status) - peso(a.status);
    if (diferenca !== 0) return diferenca;
    return String(b.date_created || "").localeCompare(String(a.date_created || ""));
  })[0];
}

export function pagamentoParaOBanco(pagamento: PagamentoDoMercadoPago) {
  return {
    id: String(pagamento.id),
    status: pagamento.status,
    status_detail: pagamento.status_detail ?? null,
    payment_type_id: pagamento.payment_type_id ?? null,
    transaction_amount: pagamento.transaction_amount ?? null,
    date_approved: pagamento.date_approved ?? null,
  };
}

/**
 * Confere o `x-signature` do aviso do Mercado Pago.
 *
 * Manifesto: `id:{data.id};request-id:{x-request-id};ts:{ts};` (partes
 * ausentes saem do manifesto), HMAC-SHA256 em hexadecimal com a chave secreta
 * do aviso. O `data.id` vem da query string e, sendo alfanumérico, em
 * minúsculas.
 */
export function avisoDoMercadoPagoValido(input: {
  segredo: string;
  assinatura: string | null;
  idDaRequisicao: string | null;
  idDoDado: string | null;
  agoraEmSegundos?: number;
  toleranciaEmSegundos?: number;
}) {
  if (!input.assinatura) return false;
  const partes = new Map<string, string>();
  for (const parte of input.assinatura.split(",")) {
    const [chave, ...valor] = parte.split("=");
    if (chave && valor.length) partes.set(chave.trim(), valor.join("=").trim());
  }
  const ts = partes.get("ts");
  const v1 = partes.get("v1");
  if (!ts || !v1) return false;

  const tolerancia = input.toleranciaEmSegundos ?? 60 * 60;
  const agora = input.agoraEmSegundos ?? Math.floor(Date.now() / 1000);
  const instante = Number(ts.length > 10 ? Number(ts) / 1000 : ts);
  if (!Number.isFinite(instante) || Math.abs(agora - instante) > tolerancia) return false;

  let manifesto = "";
  if (input.idDoDado) manifesto += `id:${/^[a-z0-9]+$/i.test(input.idDoDado) ? input.idDoDado.toLowerCase() : input.idDoDado};`;
  if (input.idDaRequisicao) manifesto += `request-id:${input.idDaRequisicao};`;
  manifesto += `ts:${ts};`;

  const esperada = Buffer.from(createHmac("sha256", input.segredo).update(manifesto).digest("hex"));
  const recebida = Buffer.from(v1);
  return esperada.length === recebida.length && timingSafeEqual(esperada, recebida);
}
