import test from "node:test";
import assert from "node:assert/strict";

import { esquecerLoja, lembrar } from "./lembranca.ts";

/*
  A lembrança da loja quebra calada: quando erra, o lojista troca o tema,
  recarrega a loja e vê a antiga, sem erro nenhum na tela. Cada caso aqui é
  uma forma de isso acontecer.
*/

/* Uma busca que só responde quando o teste manda, para montar a corrida. */
function buscaNaMao<T>() {
  let responder: (valor: T) => void = () => undefined;
  const promessa = new Promise<T>((resolve) => {
    responder = resolve;
  });
  let chamadas = 0;
  return {
    buscar: () => {
      chamadas += 1;
      return promessa;
    },
    responder: (valor: T) => responder(valor),
    chamadas: () => chamadas,
  };
}

let serie = 0;
/* Loja nova a cada teste: a memória é do processo e os testes dividem ela. */
const lojaNova = () => `loja-de-teste-${++serie}`;

test("a segunda visita não vai ao banco", async () => {
  const loja = lojaNova();
  let idas = 0;
  const buscar = async () => ({ tema: "default", ida: ++idas });

  assert.deepEqual(await lembrar(loja, buscar), { tema: "default", ida: 1 });
  assert.deepEqual(await lembrar(loja, buscar), { tema: "default", ida: 1 });
  assert.equal(idas, 1);
});

test("duas visitas ao mesmo tempo dividem a mesma ida ao banco", async () => {
  const loja = lojaNova();
  const banco = buscaNaMao<string>();
  const primeira = lembrar(loja, banco.buscar);
  const segunda = lembrar(loja, banco.buscar);
  banco.responder("vitrine");

  assert.equal(await primeira, "vitrine");
  assert.equal(await segunda, "vitrine");
  assert.equal(banco.chamadas(), 1);
});

test("o aviso apaga a vitrine e os produtos da loja, e só dela", async () => {
  const loja = lojaNova();
  const vizinha = lojaNova();
  let idas = 0;
  const buscar = async () => ++idas;

  await lembrar(loja, buscar);
  await lembrar(`${loja}/produto/1`, buscar);
  await lembrar(vizinha, buscar);
  assert.equal(idas, 3);

  esquecerLoja(loja);

  await lembrar(loja, buscar);
  await lembrar(`${loja}/produto/1`, buscar);
  await lembrar(vizinha, buscar);
  assert.equal(idas, 5, "a loja avisada voltou ao banco duas vezes; a vizinha, nenhuma");
});

test("loja com nome parecido não é apagada junto", async () => {
  const loja = lojaNova();
  let idas = 0;
  const buscar = async () => ++idas;

  await lembrar(`${loja}-filial`, buscar);
  esquecerLoja(loja);
  await lembrar(`${loja}-filial`, buscar);
  assert.equal(idas, 1);
});

test("busca que saiu antes do aviso não guarda o retrato velho", async () => {
  const loja = lojaNova();
  const antes = buscaNaMao<string>();

  /* Um visitante abre a loja; o banco ainda está respondendo... */
  const visitante = lembrar(loja, antes.buscar);
  /* ...o lojista troca o tema e o admin avisa... */
  esquecerLoja(loja);
  /* ...e só agora o banco responde, com o que leu ANTES da troca. */
  antes.responder("tema velho");
  assert.equal(await visitante, "tema velho", "quem já esperava recebe o que o banco trouxe");

  let idas = 0;
  const depois = await lembrar(loja, async () => {
    idas += 1;
    return "tema novo";
  });
  assert.equal(depois, "tema novo");
  assert.equal(idas, 1, "o retrato velho não ficou na memória");
});

test("quem chega depois do aviso não pega carona na busca de antes", async () => {
  const loja = lojaNova();
  const antes = buscaNaMao<string>();
  const visitante = lembrar(loja, antes.buscar);

  esquecerLoja(loja);

  const lojista = lembrar(loja, async () => "tema novo");
  antes.responder("tema velho");

  assert.equal(await lojista, "tema novo");
  assert.equal(await visitante, "tema velho");
  /* E o que fica guardado é o novo. */
  assert.equal(await lembrar(loja, async () => "não deveria buscar"), "tema novo");
});

test("endereço digitado com maiúscula é apagado pelo mesmo aviso", async () => {
  const loja = lojaNova();
  let idas = 0;
  const buscar = async () => ++idas;

  await lembrar(`${loja.toUpperCase()}/produto/9`, buscar);
  esquecerLoja(loja);
  await lembrar(`${loja.toUpperCase()}/produto/9`, buscar);
  assert.equal(idas, 2);
});

test("nulo não fica guardado: o produto pode nascer daqui a pouco", async () => {
  const loja = lojaNova();
  let idas = 0;
  const buscar = async () => {
    idas += 1;
    return idas === 1 ? null : "nasceu";
  };

  assert.equal(await lembrar(`${loja}/produto/7`, buscar), null);
  assert.equal(await lembrar(`${loja}/produto/7`, buscar), "nasceu");
});

test("busca que falha não trava a fila", async () => {
  const loja = lojaNova();
  await assert.rejects(lembrar(loja, async () => Promise.reject(new Error("banco fora"))));
  assert.equal(await lembrar(loja, async () => "voltou"), "voltou");
});
