import assert from "node:assert/strict";
import { createHmac } from "node:crypto";
import { describe, it } from "node:test";

import {
  assinaturaValida,
  decidir,
  diasQueFaltam,
  donoDaMarca,
  fimDoDiaEmBrasilia,
  lerAviso,
  lerPedidoDaApi,
  linkDePagamento,
  marcaDaEmpresa,
  pagamentoDoAviso,
  pagoAteDoAviso,
  planoDoPedido,
  PRODUTO_KTALOGU,
  segredoDoCorpoValido,
  vencimentoAoLiberar,
} from "./assinatura-cakto.ts";

const SEGREDO = "segredo-de-teste";

/* O formato real de uma entrega da Cakto (29/09/2026), reduzido ao que importa. */
function entrega(evento: string, mudancas: Record<string, unknown> = {}, assinatura: Record<string, unknown> = {}) {
  return {
    secret: SEGREDO,
    event: evento,
    data: {
      id: "pedido-1",
      status: "paid",
      paidAt: "2026-09-29T14:07:10.216170-03:00",
      callback: null,
      customer: { id: 8563540, name: "Maria", email: " Maria@Exemplo.com " },
      product: { id: PRODUTO_KTALOGU, name: "Ktalogu Gestão" },
      offer: { id: "b9jcqix", price: 69.9 },
      subscription: {
        id: "assinatura-1",
        offer: "b9jcqix",
        product: PRODUTO_KTALOGU,
        next_payment_date: "2026-10-29T14:07:05.713295-03:00",
        ...assinatura,
      },
      ...mudancas,
    },
  };
}

function aviso(evento: string, mudancas: Record<string, unknown> = {}, assinatura: Record<string, unknown> = {}) {
  const lido = lerAviso(entrega(evento, mudancas, assinatura));
  assert.ok(lido);
  return lido;
}

describe("lendo a entrega", () => {
  it("tira do corpo o que a regra usa", () => {
    const lido = aviso("purchase_approved");
    assert.equal(lido.chave, "purchase_approved:pedido-1");
    assert.equal(lido.email, "maria@exemplo.com");
    assert.equal(lido.clienteId, "8563540");
    assert.equal(lido.assinaturaId, "assinatura-1");
    assert.equal(lido.ofertaId, "b9jcqix");
    assert.equal(lido.pagoEm?.toISOString(), "2026-09-29T17:07:10.216Z");
  });

  it("abandono de checkout e lista do webhook V2 não viram aviso de pedido", () => {
    assert.equal(lerAviso({ event: "checkout_abandonment", data: { customerEmail: "x@y.com" } }), null);
    assert.equal(lerAviso({ event: "purchase_approved", data: [{ id: "a" }] }), null);
    assert.equal(lerAviso("lixo"), null);
  });
});

describe("o que cada aviso faz", () => {
  it("compra aprovada libera até a próxima cobrança", () => {
    const acao = decidir(aviso("purchase_approved"));
    assert.equal(acao.tipo, "liberar");
    assert.equal(acao.tipo === "liberar" && acao.pagoAte.toISOString(), "2026-10-29T17:07:05.713Z");
  });

  it("no Pix a próxima cobrança vem vazia e o período conta do pagamento", () => {
    const acao = decidir(aviso("purchase_approved", { offer: { id: "gvndd3a" } }, { offer: "gvndd3a", next_payment_date: null }));
    assert.equal(acao.tipo === "liberar" && acao.pagoAte.toISOString(), "2027-09-29T17:07:10.216Z");
  });

  it("renovação, recuperação e reativação também liberam", () => {
    for (const evento of ["subscription_created", "subscription_renewed", "subscription_late_recovered", "subscription_resumed"]) {
      assert.equal(decidir(aviso(evento)).tipo, "liberar", evento);
    }
  });

  it("pedido que não está pago não libera", () => {
    assert.equal(decidir(aviso("purchase_approved", { status: "waiting_payment" })).tipo, "ignorar");
  });

  it("outro produto da mesma conta Cakto não mexe na Ktalogu", () => {
    const outro = aviso("purchase_approved", { product: { id: "50777bc2-1b7c-4d6f-803c-41a12c6e5f97" } }, { product: "x" });
    assert.equal(decidir(outro).tipo, "ignorar");
  });

  it("oferta que não é uma das duas não libera, mesmo com nome de anual", () => {
    const estranha = aviso("purchase_approved", { offer: { id: "3c6bkmp", name: "Ktalogu - Anual" } }, { offer: "3c6bkmp" });
    assert.equal(decidir(estranha).tipo, "ignorar");
  });

  it("renovação recusada e atraso só marcam: o acesso já fecha no dia seguinte ao vencimento", () => {
    assert.deepEqual(decidir(aviso("subscription_renewal_refused")), { tipo: "marcar", situacao: "atrasada" });
    assert.deepEqual(decidir(aviso("subscription_late")), { tipo: "marcar", situacao: "atrasada" });
  });

  it("cancelar mantém até o fim do que foi pago", () => {
    assert.deepEqual(decidir(aviso("subscription_canceled")), { tipo: "marcar", situacao: "cancelada" });
  });

  it("reembolso e chargeback cortam na hora", () => {
    assert.deepEqual(decidir(aviso("refund", { status: "refunded" })), { tipo: "cortar", situacao: "reembolsada" });
    assert.deepEqual(decidir(aviso("chargeback", { status: "chargedback" })), { tipo: "cortar", situacao: "contestada" });
  });

  it("pedido de reembolso, Pix gerado e recusa só ficam registrados", () => {
    for (const evento of ["refund_requested", "pix_gerado", "purchase_refused"]) {
      assert.equal(decidir(aviso(evento)).tipo, "ignorar", evento);
    }
  });
});

describe("até quando vale", () => {
  const pago = new Date("2026-09-29T17:00:00Z");

  it("data da próxima cobrança fora do razoável não é aceita", () => {
    assert.equal(pagoAteDoAviso({ pagoEm: pago, proximoPagamento: new Date("2026-09-29T18:00:00Z") }, 30)?.toISOString(), "2026-10-29T17:00:00.000Z");
    assert.equal(pagoAteDoAviso({ pagoEm: pago, proximoPagamento: new Date("2027-09-29T17:00:00Z") }, 30)?.toISOString(), "2026-10-29T17:00:00.000Z");
  });

  it("o acesso vai até 23:59:59 de Brasília do dia do vencimento", () => {
    assert.equal(fimDoDiaEmBrasilia(new Date("2026-10-29T17:07:05Z")).toISOString(), "2026-10-30T02:59:59.000Z");
    /* 01:00 UTC do dia 30 ainda é dia 29 em Brasília. */
    assert.equal(fimDoDiaEmBrasilia(new Date("2026-10-30T01:00:00Z")).toISOString(), "2026-10-30T02:59:59.000Z");
  });

  it("pagar não encurta um acesso que já valia mais, e não soma duas vezes", () => {
    const pagoAte = new Date("2026-10-29T17:00:00Z");
    const longe = new Date("2028-01-01T00:00:00Z");
    assert.equal(vencimentoAoLiberar(longe, true, pagoAte).toISOString(), longe.toISOString());
    const primeiro = vencimentoAoLiberar(null, false, pagoAte);
    assert.equal(vencimentoAoLiberar(primeiro, true, pagoAte).toISOString(), primeiro.toISOString());
  });

  it("acesso cortado não segura a data antiga", () => {
    const longe = new Date("2028-01-01T00:00:00Z");
    assert.equal(vencimentoAoLiberar(longe, false, new Date("2026-10-29T17:00:00Z")).toISOString(), "2026-10-30T02:59:59.000Z");
  });
});

describe("origem da entrega", () => {
  const corpo = JSON.stringify(entrega("purchase_approved"));
  const agora = 1_790_000_000;
  const assinar = (ts: number, texto = corpo) => `v1=${createHmac("sha256", SEGREDO).update(`${ts}.`).update(texto).digest("hex")}`;

  it("aceita a assinatura certa", () => {
    assert.equal(assinaturaValida(corpo, String(agora), assinar(agora), SEGREDO, agora), true);
    assert.equal(assinaturaValida(corpo, String(agora), `v2=abc, ${assinar(agora)}`, SEGREDO, agora), true);
  });

  it("recusa corpo mexido, segredo errado e entrega velha", () => {
    assert.equal(assinaturaValida(corpo + " ", String(agora), assinar(agora), SEGREDO, agora), false);
    assert.equal(assinaturaValida(corpo, String(agora), assinar(agora), "outro", agora), false);
    assert.equal(assinaturaValida(corpo, String(agora - 600), assinar(agora - 600), SEGREDO, agora), false);
    assert.equal(assinaturaValida(corpo, "", assinar(agora), SEGREDO, agora), false);
  });

  it("o segredo do corpo só confere igual", () => {
    assert.equal(segredoDoCorpoValido({ secret: SEGREDO }, SEGREDO), true);
    assert.equal(segredoDoCorpoValido({ secret: "quase" }, SEGREDO), false);
    assert.equal(segredoDoCorpoValido({}, SEGREDO), false);
    assert.equal(segredoDoCorpoValido({ secret: "" }, ""), false);
  });
});

describe("marca da empresa no link", () => {
  const dono = "6f1c2a3b-4d5e-4f60-8a7b-9c0d1e2f3a4b";

  it("vai e volta, e só com caracteres que a Cakto aceita", () => {
    const marca = marcaDaEmpresa(dono, SEGREDO);
    assert.ok(marca);
    assert.match(marca, /^[A-Za-z0-9._~-]+$/);
    assert.equal(donoDaMarca(marca, SEGREDO), dono);
    assert.equal(linkDePagamento("b9jcqix", marca), `https://pay.cakto.com.br/b9jcqix?callback=${marca}`);
  });

  it("marca trocada ou com outro segredo não vale", () => {
    const marca = marcaDaEmpresa(dono, SEGREDO)!;
    const outroDono = marca.replace(/^k1\.[0-9a-f]/, (m) => (m.endsWith("0") ? "k1.1" : "k1.0"));
    assert.equal(donoDaMarca(outroDono, SEGREDO), null);
    assert.equal(donoDaMarca(marca, "outro"), null);
    assert.equal(donoDaMarca("k1.lixo.lixo", SEGREDO), null);
    assert.equal(donoDaMarca(null, SEGREDO), null);
  });

  it("sem segredo não há marca, e o link sai limpo", () => {
    assert.equal(marcaDaEmpresa(dono, ""), null);
    assert.equal(linkDePagamento("gvndd3a", null), "https://pay.cakto.com.br/gvndd3a");
  });
});

describe("troca de plano", () => {
  const agora = new Date("2026-10-10T15:00:00Z");

  it("os dias que faltam contam dia começado como inteiro", () => {
    assert.equal(diasQueFaltam(new Date("2026-10-28T02:59:59Z"), agora), 18);
    assert.equal(diasQueFaltam(new Date("2026-10-10T16:00:00Z"), agora), 1);
    assert.equal(diasQueFaltam(new Date("2026-10-09T00:00:00Z"), agora), 0);
    assert.equal(diasQueFaltam(null, agora), 0);
  });

  it("o crédito entra somado ao fim do período pago", () => {
    const pagoAte = new Date("2027-10-10T15:00:00Z");
    assert.equal(vencimentoAoLiberar(null, false, pagoAte, 18).toISOString(), "2027-10-29T02:59:59.000Z");
  });

  it("upgrade com o mensal ainda valendo não soma os dias duas vezes", () => {
    const mensalAte = new Date("2026-10-28T02:59:59Z");
    const credito = diasQueFaltam(mensalAte, agora);
    const vence = vencimentoAoLiberar(mensalAte, true, new Date("2027-10-10T15:00:00Z"), credito);
    assert.equal(vence.toISOString(), "2027-10-29T02:59:59.000Z");
  });

  it("a renovação seguinte mantém o crédito", () => {
    const primeiro = vencimentoAoLiberar(null, false, new Date("2027-10-10T15:00:00Z"), 18);
    const renovado = vencimentoAoLiberar(primeiro, true, new Date("2028-10-09T15:00:00Z"), 18);
    assert.equal(renovado.toISOString(), "2028-10-28T02:59:59.000Z");
  });
});

describe("plano dos pedidos", () => {
  it("planos novos pela oferta, antigos pelo produto", () => {
    assert.equal(planoDoPedido(PRODUTO_KTALOGU, "gvndd3a"), "anual");
    assert.equal(planoDoPedido(PRODUTO_KTALOGU, "b9jcqix"), "mensal");
    assert.equal(planoDoPedido(PRODUTO_KTALOGU, "outra"), null);
    assert.equal(planoDoPedido("16d34667-ef62-4c43-8d46-3ab93fda7385", ""), "mensal");
    assert.equal(planoDoPedido("aa9796b5-6abd-4c5c-88eb-2c8361b9f42e", ""), "anual");
    assert.equal(planoDoPedido("05903b83-1332-4989-b6ed-f3f829341669", ""), null);
  });
});

describe("histórico de pagamentos", () => {
  it("o aviso vira uma linha do histórico", () => {
    const linha = pagamentoDoAviso(aviso("purchase_approved", { amount: 69.9, paymentMethod: "credit_card", refId: "ABC123" }));
    assert.equal(linha?.pedido_id, "pedido-1");
    assert.equal(linha?.plano, "mensal");
    assert.equal(linha?.valor, 69.9);
    assert.equal(linha?.metodo, "credit_card");
    assert.equal(linha?.status, "paid");
  });

  it("o pedido da API de pedidos também", () => {
    const lido = lerPedidoDaApi({
      id: "p-9",
      refId: "9aLqj",
      status: "paid",
      product: { id: "aa9796b5-6abd-4c5c-88eb-2c8361b9f42e" },
      subscription: "s-1",
      customer: { email: "Cliente@Exemplo.com" },
      amount: "147.99",
      paymentMethod: "pix",
      checkoutUrl: "https://pay.cakto.com.br/q5srjif_706033",
      createdAt: "2026-09-29T13:37:24-03:00",
      paidAt: "2026-09-29T13:37:40-03:00",
    });
    assert.equal(lido?.email, "cliente@exemplo.com");
    assert.equal(lido?.pagamento.plano, "anual");
    assert.equal(lido?.pagamento.valor, 147.99);
    assert.equal(lido?.pagamento.oferta_id, "q5srjif");
    assert.equal(lido?.pagamento.assinatura_id, "s-1");
  });

  it("pedido de outro produto (Pensão Paga) fica de fora", () => {
    assert.equal(lerPedidoDaApi({ id: "x", product: { id: "05903b83-1332-4989-b6ed-f3f829341669" }, customer: { email: "a@b.com" } }), null);
  });
});
