import assert from "node:assert/strict";
import { test } from "node:test";
import {
  billingTypeDoMeio,
  BASE_DO_ASAAS,
  EVENTOS_DO_WEBHOOK,
  credenciaisAtivas,
  estadoDoPagamento,
  gerarTokenDeWebhook,
  instanteDoPagamento,
  lerConfiguracaoDoAsaas,
  mensagemDoErro,
  metodoDoPagamento,
  vencimentoNoBrasil,
} from "./asaas.ts";

/* ------------------------------------------------------- os dois ambientes */

test("sandbox e producao sao bases diferentes, nunca a mesma", () => {
  assert.equal(BASE_DO_ASAAS.production, "https://api.asaas.com/v3");
  assert.equal(BASE_DO_ASAAS.sandbox, "https://api-sandbox.asaas.com/v3");
  assert.notEqual(BASE_DO_ASAAS.production, BASE_DO_ASAAS.sandbox);
});

test("em producao vale a chave de producao, e sem ela nao ha credencial", () => {
  const config = lerConfiguracaoDoAsaas({
    environment: "production",
    sandbox_api_key: "chave_de_sandbox",
    production_api_key: "",
  });

  /* A chave de sandbox esta preenchida, mas nao serve: uma chave de sandbox
     usada em producao cobra no lugar errado e nao cobra no certo. */
  assert.equal(credenciaisAtivas(config), null);
});

test("o ambiente escolhido manda na chave, nao a ordem em que foram digitadas", () => {
  const bruto = { sandbox_api_key: "aaa", production_api_key: "bbb" };

  assert.deepEqual(credenciaisAtivas(lerConfiguracaoDoAsaas({ ...bruto, environment: "sandbox" })), {
    ambiente: "sandbox",
    apiKey: "aaa",
  });
  assert.deepEqual(credenciaisAtivas(lerConfiguracaoDoAsaas({ ...bruto, environment: "production" })), {
    ambiente: "production",
    apiKey: "bbb",
  });
});

test("ambiente desconhecido cai em sandbox, que e o lado que nao move dinheiro", () => {
  assert.equal(lerConfiguracaoDoAsaas({ environment: "prod" }).environment, "sandbox");
  assert.equal(lerConfiguracaoDoAsaas(null).environment, "sandbox");
});

/* -------------------------------------------------- a leitura da config */

test("integracao nova nasce com Pix e boleto; cartao fica desligado", () => {
  /* Pix e boleto acontecem dentro da loja, do jeito que o cliente conhece. O
     cartao pelo Asaas leva o comprador para uma pagina deles e o traz de
     volta — mandar alguem para fora da loja sem o lojista ter pedido seria
     decidir no lugar dele. */
  const config = lerConfiguracaoDoAsaas({});
  assert.equal(config.aceita_pix, true);
  assert.equal(config.aceita_boleto, true);
  assert.equal(config.aceita_cartao, false);
});

test("o billingType de cada meio e o nome que o Asaas espera", () => {
  assert.equal(billingTypeDoMeio("pix"), "PIX");
  assert.equal(billingTypeDoMeio("boleto"), "BOLETO");
  assert.equal(billingTypeDoMeio("cartao"), "CREDIT_CARD");
});

test("dias de vencimento fora da faixa voltam para o padrao de um dia", () => {
  assert.equal(lerConfiguracaoDoAsaas({ dias_para_vencimento: 7 }).dias_para_vencimento, 7);
  assert.equal(lerConfiguracaoDoAsaas({ dias_para_vencimento: 0 }).dias_para_vencimento, 0);
  assert.equal(lerConfiguracaoDoAsaas({ dias_para_vencimento: -3 }).dias_para_vencimento, 1);
  assert.equal(lerConfiguracaoDoAsaas({ dias_para_vencimento: 900 }).dias_para_vencimento, 1);
  assert.equal(lerConfiguracaoDoAsaas({ dias_para_vencimento: "abc" }).dias_para_vencimento, 1);
});

/* --------------------------------------------------- a traducao de estados */

test("confirmado e recebido liberam o pedido; a diferenca entre eles nao e do cliente", () => {
  /* CONFIRMED e pago com o dinheiro ainda indisponivel na conta do lojista.
     Segurar a entrega ate o dinheiro estar sacavel seria punir quem ja pagou. */
  assert.equal(estadoDoPagamento("CONFIRMED"), "approved");
  assert.equal(estadoDoPagamento("RECEIVED"), "approved");
  assert.equal(estadoDoPagamento("RECEIVED_IN_CASH"), "approved");
});

test("boleto vencido continua pendente, porque vencido ainda se paga", () => {
  assert.equal(estadoDoPagamento("OVERDUE"), "pending");
  assert.equal(estadoDoPagamento("PENDING"), "pending");
  assert.equal(estadoDoPagamento("AWAITING_RISK_ANALYSIS"), "pending");
});

test("estorno e contestacao nao se confundem", () => {
  assert.equal(estadoDoPagamento("REFUNDED"), "refunded");
  assert.equal(estadoDoPagamento("PARTIALLY_REFUNDED"), "refunded");
  assert.equal(estadoDoPagamento("CHARGEBACK_REQUESTED"), "charged_back");
  assert.equal(estadoDoPagamento("CHARGEBACK_DISPUTE"), "charged_back");
});

test("cobranca apagada e cancelada, e recusa de risco e recusa", () => {
  assert.equal(estadoDoPagamento("DELETED"), "cancelled");
  assert.equal(estadoDoPagamento("REPROVED_BY_RISK_ANALYSIS"), "rejected");
});

test("estado que o Asaas invente amanha vira pendente, nunca aprovado", () => {
  /* O unico erro caro aqui e dar por pago o que nao foi. */
  assert.equal(estadoDoPagamento("ALGO_NOVO"), "pending");
  assert.equal(estadoDoPagamento(null), "pending");
  assert.equal(estadoDoPagamento(""), "pending");
});

test("o estado chega em maiuscula, mas minuscula tambem tem que passar", () => {
  assert.equal(estadoDoPagamento("received"), "approved");
});

/* ---------------------------------------------------------- o meio de pagar */

test("os meios traduzidos sao os que a restricao do banco aceita", () => {
  /* `payment_method` em catalog_order_payments so aceita esta lista desde a
     migration 82. Boleto e `ticket` la, e escrever "boleto" quebra o INSERT. */
  const aceitos = ["unknown", "pix", "credit_card", "debit_card", "ticket", "wallet"];
  for (const tipo of ["PIX", "BOLETO", "CREDIT_CARD", "DEBIT_CARD", "UNDEFINED", ""]) {
    assert.ok(aceitos.includes(metodoDoPagamento(tipo)), `${tipo} virou algo que o banco recusa`);
  }
  assert.equal(metodoDoPagamento("BOLETO"), "ticket");
  assert.equal(metodoDoPagamento("PIX"), "pix");
});

/* ------------------------------------------------------------ o vencimento */

test("o vencimento e o dia no Brasil, nao o dia do servidor em UTC", () => {
  /* 02:00 UTC de 13/09 ainda e 23:00 do dia 12 em Brasilia. Uma cobranca de
     balcao criada a essa hora nao pode nascer vencendo "amanha". */
  const madrugada = new Date("2026-09-13T02:00:00.000Z");
  assert.equal(vencimentoNoBrasil(0, madrugada), "2026-09-12");

  const meioDia = new Date("2026-09-13T15:00:00.000Z");
  assert.equal(vencimentoNoBrasil(0, meioDia), "2026-09-13");
});

test("os dias a frente contam a partir do dia brasileiro", () => {
  const meioDia = new Date("2026-09-13T15:00:00.000Z");
  assert.equal(vencimentoNoBrasil(1, meioDia), "2026-09-14");
  assert.equal(vencimentoNoBrasil(30, meioDia), "2026-10-13");
});

/* ------------------------------------------------------------- o webhook */

test("o token do webhook cabe na regra do Asaas e nunca se repete", () => {
  const a = gerarTokenDeWebhook();
  const b = gerarTokenDeWebhook();

  assert.match(a, /^[0-9a-f]{48}$/);
  assert.ok(a.length >= 32 && a.length <= 255, "fora da faixa que o Asaas aceita");
  assert.ok(!a.includes(" "), "token com espaco o Asaas recusa");
  assert.notEqual(a, b);
});

test("pedimos so os eventos de cobranca, e nenhum deles de split", () => {
  assert.ok(EVENTOS_DO_WEBHOOK.includes("PAYMENT_CONFIRMED"));
  assert.ok(EVENTOS_DO_WEBHOOK.includes("PAYMENT_RECEIVED"));
  /* Sem split nesta integracao: a Ktalogu nao fica com fatia de nada. */
  assert.ok(!EVENTOS_DO_WEBHOOK.some((evento) => evento.includes("SPLIT")));
  for (const evento of EVENTOS_DO_WEBHOOK) assert.match(evento, /^PAYMENT_/);
});

/* ---------------------------------------------------------------- o erro */

test("o motivo real da recusa chega ate o lojista", () => {
  /* Sem desempacotar `errors[]`, o lojista le "nao foi possivel falar com o
     Asaas" quando a resposta dizia "CPF invalido" — e abre chamado em vez de
     corrigir o campo. */
  const corpo = { errors: [{ code: "invalid_cpfCnpj", description: "O CPF informado e invalido." }] };
  assert.equal(mensagemDoErro(corpo), "O CPF informado e invalido.");
});

test("varios erros viram uma frase so", () => {
  const corpo = { errors: [{ description: "Primeiro." }, { description: "Segundo." }] };
  assert.equal(mensagemDoErro(corpo), "Primeiro. Segundo.");
});

test("corpo sem forma conhecida cai no aviso generico", () => {
  assert.equal(mensagemDoErro(null), "Não foi possível falar com o Asaas.");
  assert.equal(mensagemDoErro({ errors: [] }), "Não foi possível falar com o Asaas.");
  assert.equal(mensagemDoErro("<html>502</html>"), "Não foi possível falar com o Asaas.");
});


/* ------------------------------------------ o instante do pagamento */

test("data pura do Asaas vira meio-dia de Brasilia, nao meia-noite UTC", () => {
  /* O Asaas manda "2026-09-14" sem hora. Guardar assim numa coluna de instante
     vira meia-noite UTC, que no Brasil e 21h do DIA ANTERIOR: um Pix pago no
     dia 14 caia no fluxo de caixa como dia 13. */
  const instante = instanteDoPagamento("2026-09-14");
  assert.equal(instante, "2026-09-14T15:00:00.000Z");

  /* A prova do que importa: o dia continua 14 quando lido no Brasil. */
  const noBrasil = new Date(new Date(instante as string).getTime() - 3 * 60 * 60 * 1000);
  assert.equal(noBrasil.toISOString().slice(0, 10), "2026-09-14");
});

test("meia-noite UTC seria o dia errado no Brasil", () => {
  /* O comportamento antigo, guardado aqui para ninguem voltar a ele sem querer. */
  const errado = new Date(new Date("2026-09-14T00:00:00.000Z").getTime() - 3 * 60 * 60 * 1000);
  assert.equal(errado.toISOString().slice(0, 10), "2026-09-13");
});

test("instante completo passa inteiro, sem inventar hora", () => {
  assert.equal(instanteDoPagamento("2026-09-14T18:42:10.000Z"), "2026-09-14T18:42:10.000Z");
});

test("vazio e lixo viram nada, nunca uma data inventada", () => {
  assert.equal(instanteDoPagamento(""), null);
  assert.equal(instanteDoPagamento(null), null);
  assert.equal(instanteDoPagamento("ontem"), null);
});
