import assert from "node:assert/strict";
import { test } from "node:test";
import {
  LOCAIS,
  MEIOS,
  type EstadoDePagamento,
  type LinhaDaIntegracao,
  type Local,
  type Rota,
  integracaoServeParaCobrar,
  meiosDoLocal,
  meiosPossiveis,
  provedorDoMeio,
  provedorSabe,
} from "./provedor-de-pagamento.ts";

/*
  Quem cobra, onde e por qual meio.

  Este e o arquivo mais perigoso da integracao: uma regra errada aqui nao quebra
  a tela, ela manda o dinheiro do cliente para a conta errada, ou apaga um meio
  de pagamento sem ninguem perceber. Por isso cada cenario esta escrito, e os
  que "nunca vao acontecer" tambem.
*/

const mercadoPago = (extras: Partial<LinhaDaIntegracao> = {}, settings: Record<string, unknown> = {}): LinhaDaIntegracao => ({
  id: "mp",
  user_id: "dono",
  provider: "mercado_pago",
  settings: {
    environment: "production",
    production_public_key: "APP_USR-pub",
    production_access_token: "APP_USR-tok",
    ...settings,
  },
  active: true,
  updated_at: "2026-01-01T00:00:00.000Z",
  ...extras,
});

const asaas = (extras: Partial<LinhaDaIntegracao> = {}, settings: Record<string, unknown> = {}): LinhaDaIntegracao => ({
  id: "as",
  user_id: "dono",
  provider: "asaas",
  settings: { environment: "production", production_api_key: "$aact_x", ...settings },
  active: true,
  updated_at: "2026-01-01T00:00:00.000Z",
  ...extras,
});

/* Os tipos do Next marcam `NODE_ENV` como somente leitura, e estes testes
   precisam justamente trocar de ambiente para provar a trava do sandbox. */
const ambiente = process.env as Record<string, string | undefined>;

const estado = (integracoes: LinhaDaIntegracao[], rotas: Rota[] = []): EstadoDePagamento => ({ integracoes, rotas });
const quem = (e: EstadoDePagamento, local: Local, meio: "pix" | "cartao" | "boleto") =>
  provedorDoMeio(e, local, meio)?.provedor ?? null;

/* ------------------------------------------- o que cada provedor sabe fazer */

test("o cartao pelo Asaas existe so na loja, porque so la cabe a fatura hospedada", () => {
  /* O Asaas nao tem tokenizacao no navegador: cartao transparente por ele
     colocaria o numero do cartao dentro do nosso servidor. O caminho e a
     fatura hospedada, que precisa de um navegador para onde mandar o
     comprador — e por isso ela existe na loja e em nenhum outro lugar. */
  assert.equal(provedorSabe("asaas", "loja", "cartao"), true);
  for (const local of ["balcao", "link", "assinatura"] as Local[]) {
    assert.equal(provedorSabe("asaas", local, "cartao"), false, `asaas nao faz cartao em ${local}`);
  }
});

test("assinatura pelo Asaas nao existe, e o motivo e o mesmo do cartao transparente", () => {
  /* Cobrar de novo sozinho exige guardar o cartao, e guardar cartao no Asaas
     exige receber o numero. A fatura hospedada nao resolve isso: ela serve
     uma vez, com o comprador na frente da tela. */
  assert.equal(provedorSabe("asaas", "assinatura", "cartao"), false);
});

test("no balcao so existe Pix, porque cartao ali e a maquininha", () => {
  assert.deepEqual(meiosPossiveis("balcao"), ["pix"]);
});

test("na assinatura so existe cartao, porque so ele cobra sozinho de novo", () => {
  assert.deepEqual(meiosPossiveis("assinatura"), ["cartao"]);
});

test("a loja e o link oferecem os tres meios", () => {
  assert.deepEqual(meiosPossiveis("loja"), ["pix", "cartao", "boleto"]);
  assert.deepEqual(meiosPossiveis("link"), ["pix", "cartao", "boleto"]);
});

/* ---------------------------------------------- quem serve para cobrar */

test("integracao desligada ou com meia credencial nao cobra em lugar nenhum", () => {
  for (const local of LOCAIS) {
    assert.equal(integracaoServeParaCobrar(mercadoPago({ active: false }), local), false);
    assert.equal(integracaoServeParaCobrar(asaas({ active: false }), local), false);
    assert.equal(integracaoServeParaCobrar(mercadoPago({}, { production_access_token: "" }), local), false);
    assert.equal(integracaoServeParaCobrar(asaas({}, { production_api_key: "" }), local), false);
  }
});

test("loja online fechada nao apaga o Pix do caixa", () => {
  /* Quem so vende presencial mantem o checkout do catalogo desligado. Exigir
     isso no balcao tiraria o Pix do caixa de um monte de gente de uma vez. */
  const fechada = { checkout_enabled: false };
  assert.equal(integracaoServeParaCobrar(mercadoPago({}, fechada), "balcao"), true);
  assert.equal(integracaoServeParaCobrar(asaas({}, fechada), "balcao"), true);
  assert.equal(integracaoServeParaCobrar(mercadoPago({}, fechada), "loja"), false);
  assert.equal(integracaoServeParaCobrar(asaas({}, fechada), "loja"), false);
});

test("Asaas so para de cobrar quando os TRES meios estao desligados", () => {
  const tudoDesligado = { aceita_pix: false, aceita_boleto: false, aceita_cartao: false };
  assert.equal(integracaoServeParaCobrar(asaas({}, tudoDesligado), "loja"), false);
  assert.equal(integracaoServeParaCobrar(asaas({}, tudoDesligado), "balcao"), false);

  /* Com o Pix desligado e o boleto ligado, a integracao continua servindo —
     para boleto. Tratar "sem Pix" como "sem Asaas" apagaria o boleto de quem
     so quer boleto. */
  assert.equal(integracaoServeParaCobrar(asaas({}, { aceita_pix: false }), "loja"), true);
});

test("o interruptor de cada meio e por loja, e vale contra o que o provedor sabe", () => {
  /* O Asaas SABE boleto em qualquer loja. Quem decide se ele cobra boleto
     naquela loja e o lojista. */
  const so_pix = estado([asaas({}, { aceita_boleto: false, aceita_cartao: false })]);
  assert.deepEqual(meiosDoLocal(so_pix, "loja").map((m) => m.meio), ["pix"]);

  const com_cartao = estado([asaas({}, { aceita_cartao: true })]);
  assert.deepEqual(meiosDoLocal(com_cartao, "loja").map((m) => m.meio), ["pix", "cartao", "boleto"]);

  /* E o interruptor nao inventa poder: ligar cartao no balcao nao faz o Asaas
     cobrar cartao no balcao, porque a fatura hospedada nao cabe la. */
  assert.equal(quem(com_cartao, "balcao", "cartao"), null);
});

test("provedor que nao conhecemos nunca cobra", () => {
  /* A tabela de integracoes tambem guarda frete. Nenhum deles pode virar meio
     de pagamento por descuido de consulta. */
  for (const local of LOCAIS) {
    assert.equal(integracaoServeParaCobrar(mercadoPago({ provider: "melhor_envio" }), local), false);
  }
});

/* -------------------------------------- o automatico, para quem nunca mexeu */

test("sem integracao nenhuma, nada cobra", () => {
  const e = estado([]);
  for (const local of LOCAIS) assert.deepEqual(meiosDoLocal(e, local), []);
});

test("so o Mercado Pago: tudo continua como sempre foi", () => {
  const e = estado([mercadoPago()]);
  assert.deepEqual(
    meiosDoLocal(e, "loja").map((m) => [m.meio, m.provedor]),
    [["pix", "mercado_pago"], ["cartao", "mercado_pago"], ["boleto", "mercado_pago"]]
  );
  assert.equal(quem(e, "balcao", "pix"), "mercado_pago");
  assert.equal(quem(e, "assinatura", "cartao"), "mercado_pago");
});

test("so o Asaas: a loja cobra Pix e boleto, e cartao so se o lojista ligar", () => {
  /* Cartao nasce desligado porque manda o comprador para fora da loja. O
     cliente ve dois botoes, e nenhum caminho que daria erro. */
  const e = estado([asaas()]);
  assert.deepEqual(meiosDoLocal(e, "loja").map((m) => [m.meio, m.provedor]), [["pix", "asaas"], ["boleto", "asaas"]]);
  assert.equal(quem(e, "loja", "cartao"), null);
  assert.equal(quem(e, "assinatura", "cartao"), null);
  assert.equal(quem(e, "link", "pix"), null);

  const ligado = estado([asaas({}, { aceita_cartao: true })]);
  assert.equal(quem(ligado, "loja", "cartao"), "asaas");
});

test("os dois ligados e sem engrenagem: o Pix vai para o mais recente", () => {
  const e = estado([
    mercadoPago({ updated_at: "2026-03-01T00:00:00.000Z" }),
    asaas({ updated_at: "2026-06-01T00:00:00.000Z" }),
  ]);
  assert.equal(quem(e, "loja", "pix"), "asaas");
  /* E o cartao NAO some junto: o Asaas sabe cartao, mas nasce com ele
     desligado, entao o cartao fica onde estava. Este e o ganho inteiro do
     desenho por meio. */
  assert.equal(quem(e, "loja", "cartao"), "mercado_pago");
  /* O boleto, sim, acompanha o Pix: o Asaas faz boleto e nasce com ele
     ligado. Quem quiser o boleto no Mercado Pago escolhe na engrenagem. */
  assert.equal(quem(e, "loja", "boleto"), "asaas");
});

/* ----------------------------------------------------- a engrenagem */

test("a escolha gravada vence o automatico", () => {
  const integracoes = [
    mercadoPago({ updated_at: "2026-09-01T00:00:00.000Z" }),
    asaas({ updated_at: "2026-06-01T00:00:00.000Z" }),
  ];
  /* Sem engrenagem, o Mercado Pago ganharia por ser o mais recente. */
  assert.equal(quem(estado(integracoes), "loja", "pix"), "mercado_pago");

  const e = estado(integracoes, [{ surface: "loja", method: "pix", provider: "asaas" }]);
  assert.equal(quem(e, "loja", "pix"), "asaas");
});

test("a escolha e por local: Pix da loja no Asaas nao mexe no balcao", () => {
  const e = estado(
    [mercadoPago({ updated_at: "2026-09-01T00:00:00.000Z" }), asaas()],
    [{ surface: "loja", method: "pix", provider: "asaas" }]
  );
  assert.equal(quem(e, "loja", "pix"), "asaas");
  assert.equal(quem(e, "balcao", "pix"), "mercado_pago");
});

test("escolha para provedor desconectado cai no automatico, e a loja continua vendendo", () => {
  /* O erro caro seria a loja parar de cobrar porque a engrenagem aponta para
     uma integracao que o lojista desligou depois. */
  const e = estado(
    [mercadoPago(), asaas({ active: false })],
    [{ surface: "loja", method: "pix", provider: "asaas" }]
  );
  assert.equal(quem(e, "loja", "pix"), "mercado_pago");
});

test("escolha para combinacao impossivel e ignorada, nao quebra", () => {
  /* Alguem gravando "cartao da loja no Asaas" por API, ou uma linha velha de
     quando o Asaas ainda faria cartao. */
  const e = estado([mercadoPago(), asaas()], [{ surface: "loja", method: "cartao", provider: "asaas" }]);
  assert.equal(quem(e, "loja", "cartao"), "mercado_pago");
});

test("escolha sobrando para um meio que aquele local nao tem nao atrapalha", () => {
  const e = estado([mercadoPago(), asaas()], [{ surface: "balcao", method: "boleto", provider: "asaas" }]);
  assert.deepEqual(meiosDoLocal(e, "balcao").map((m) => [m.meio, m.provedor]), [["pix", "mercado_pago"]]);
});

test("o cenario que ele pediu: Pix da loja no Asaas, o resto no Mercado Pago", () => {
  const e = estado(
    [mercadoPago(), asaas()],
    [
      { surface: "loja", method: "pix", provider: "asaas" },
      { surface: "balcao", method: "pix", provider: "mercado_pago" },
    ]
  );

  assert.deepEqual(
    meiosDoLocal(e, "loja").map((m) => [m.meio, m.provedor]),
    [["pix", "asaas"], ["cartao", "mercado_pago"], ["boleto", "mercado_pago"]]
  );
  assert.equal(quem(e, "balcao", "pix"), "mercado_pago");
  assert.equal(quem(e, "link", "cartao"), "mercado_pago");
  assert.equal(quem(e, "assinatura", "cartao"), "mercado_pago");
});

/* --------------------------------------------------------- as bordas */

test("a ordem da consulta nao muda o resultado", () => {
  const a = estado([mercadoPago({ updated_at: "2026-03-01T00:00:00.000Z" }), asaas({ updated_at: "2026-06-01T00:00:00.000Z" })]);
  const b = estado([asaas({ updated_at: "2026-06-01T00:00:00.000Z" }), mercadoPago({ updated_at: "2026-03-01T00:00:00.000Z" })]);
  for (const local of LOCAIS) {
    for (const meio of MEIOS) assert.equal(quem(a, local, meio), quem(b, local, meio), `${local}/${meio}`);
  }
});

test("data ausente ou ilegivel nao derruba a escolha", () => {
  assert.equal(quem(estado([mercadoPago({ updated_at: null }), asaas()]), "loja", "pix"), "asaas");
  assert.equal(quem(estado([mercadoPago({ updated_at: "ontem" }), asaas()]), "loja", "pix"), "asaas");
});

test("a escolha devolve a linha inteira, porque quem cobra grava o id dela", () => {
  /* `catalog_order_payments.integration_id` aponta para esta linha: e por ela
     que a conciliacao depois sabe com qual chave perguntar o status. */
  const escolhido = provedorDoMeio(estado([asaas()]), "loja", "pix");
  assert.equal(escolhido?.integracao.id, "as");
  assert.ok(escolhido?.integracao.settings);
});

test("nenhum meio resolvido aponta para um provedor que nao sabe faze-lo", () => {
  /* A invariante que sustenta a tela inteira. */
  const e = estado(
    [mercadoPago(), asaas()],
    LOCAIS.flatMap((local) => MEIOS.map((meio) => ({ surface: local, method: meio, provider: "asaas" })))
  );
  for (const local of LOCAIS) {
    for (const item of meiosDoLocal(e, local)) {
      assert.ok(provedorSabe(item.provedor, local, item.meio), `${item.provedor} nao faz ${item.meio} em ${local}`);
    }
  }
});


/* ------------------------------------- sandbox nao cobra cliente de verdade */

test("em producao, integracao de sandbox nao cobra nada", () => {
  /* O pior erro possivel desta integracao, porque nao da erro em lugar nenhum:
     o cliente ve um QR bonito, tenta pagar, e o pedido fica pendente para
     sempre. O criterio e o ambiente do SERVIDOR, e nao uma escolha de tela. */
  const antes = ambiente.NODE_ENV;
  try {
    ambiente.NODE_ENV = "production";

    const asaasDeTeste = asaas({}, { environment: "sandbox", sandbox_api_key: "$aact_teste" });
    const mpDeTeste = mercadoPago({}, { environment: "sandbox" });

    assert.equal(integracaoServeParaCobrar(asaasDeTeste, "loja"), false);
    assert.equal(integracaoServeParaCobrar(asaasDeTeste, "balcao"), false);
    assert.equal(integracaoServeParaCobrar(mpDeTeste, "loja"), false);

    /* E a loja nao fica sem cobrar por causa disso: quem esta em producao
       assume. */
    const e = estado([mercadoPago(), asaasDeTeste]);
    assert.equal(quem(e, "loja", "pix"), "mercado_pago");
  } finally {
    ambiente.NODE_ENV = antes;
  }
});

test("fora de producao, sandbox cobra: e o unico jeito de testar sem mover dinheiro", () => {
  const asaasDeTeste = asaas({}, { environment: "sandbox", sandbox_api_key: "$aact_teste" });
  assert.equal(integracaoServeParaCobrar(asaasDeTeste, "loja"), true);
});

test("em producao, escolha gravada para a integracao de sandbox cai no automatico", () => {
  const antes = ambiente.NODE_ENV;
  try {
    ambiente.NODE_ENV = "production";
    const e = estado(
      [mercadoPago(), asaas({}, { environment: "sandbox", sandbox_api_key: "$aact_teste" })],
      [{ surface: "loja", method: "pix", provider: "asaas" }]
    );
    assert.equal(quem(e, "loja", "pix"), "mercado_pago");
  } finally {
    ambiente.NODE_ENV = antes;
  }
});
