import assert from "node:assert/strict";
import { after, afterEach, beforeEach, mock, test } from "node:test";
import {
  avisarALojaAgora,
  fetchQueAvisaALoja,
  tabelaDaVitrineGravada,
} from "./gravacoes-da-vitrine.ts";

/*
  Este arquivo fica no caminho de TODO pedido que o admin faz ao banco pelo
  navegador. Dois jeitos de errar, os dois calados: não avisar a loja (o
  lojista troca o tema e a loja não muda) ou atrapalhar o pedido (o admin
  inteiro para de gravar). Os testes cobrem os dois.
*/

const BANCO = "https://exemplo.supabase.co";

test("gravação em tabela da vitrine é reconhecida", () => {
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/profiles?id=eq.1`, "PATCH"), "profiles");
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/catalog_banners`, "POST"), "catalog_banners");
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/catalog_banners?id=eq.9&user_id=eq.1`, "delete"), "catalog_banners");
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/catalog_sections?on_conflict=id`, "POST"), "catalog_sections");
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/categories?id=eq.3`, "PATCH"), "categories");
});

test("leitura, login, arquivo, função do banco e tabela de fora não avisam", () => {
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/profiles?id=eq.1`, "GET"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/profiles?id=eq.1`, "HEAD"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/profiles?id=eq.1`, undefined), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/auth/v1/token?grant_type=refresh_token`, "POST"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/storage/v1/object/products/foto.webp`, "POST"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/rpc/profiles`, "POST"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/orders?id=eq.1`, "PATCH"), null);
  assert.equal(tabelaDaVitrineGravada(`${BANCO}/rest/v1/profiles_backup`, "POST"), null);
});

/* ---- o fetch de verdade, com navegador e rede de mentira ---- */

type Chamada = { url: string; metodo: string };
const fetchReal = globalThis.fetch;
const mundo = globalThis as unknown as { window?: unknown };
let chamadas: Chamada[] = [];
let respostaDoBanco: () => Response = () => new Response("[]", { status: 200 });

beforeEach(() => {
  chamadas = [];
  respostaDoBanco = () => new Response("[]", { status: 200 });
  mundo.window = { addEventListener: () => undefined };
  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
    const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
    chamadas.push({ url, metodo: String(init?.method || "GET") });
    if (url === "/api/loja/avisar") return Response.json({ ok: true, loja: true });
    return respostaDoBanco();
  }) as typeof fetch;
  mock.timers.enable({ apis: ["setTimeout"] });
});

afterEach(() => {
  /* Nenhum aviso fica na fila de um teste para o outro: o relógio de mentira
     é trocado a cada teste, e um relógio velho pendurado estraga o novo. */
  mock.timers.tick(60_000);
  mock.timers.reset();
});

after(() => {
  globalThis.fetch = fetchReal;
  delete mundo.window;
});

const avisos = () => chamadas.filter((chamada) => chamada.url === "/api/loja/avisar");

test("a resposta do banco volta intacta para quem pediu", async () => {
  const esperada = new Response('[{"id":1}]', { status: 201 });
  respostaDoBanco = () => esperada;
  const recebida = await fetchQueAvisaALoja(`${BANCO}/rest/v1/catalog_banners`, { method: "POST" });
  assert.equal(recebida, esperada);
  assert.deepEqual(await recebida.json(), [{ id: 1 }]);
});

test("várias gravações em sequência viram um aviso só, depois da última", async () => {
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/catalog_banners?id=eq.1`, { method: "PATCH" });
  mock.timers.tick(200);
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/catalog_banners?id=eq.2`, { method: "PATCH" });
  mock.timers.tick(200);
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/catalog_banners?id=eq.3`, { method: "PATCH" });
  assert.equal(avisos().length, 0, "enquanto as gravações chegam, o aviso espera");

  mock.timers.tick(250);
  assert.equal(avisos().length, 1);
  assert.equal(avisos()[0].metodo, "POST");
});

test("trocar o tema avisa a loja", async () => {
  await fetchQueAvisaALoja(new URL(`${BANCO}/rest/v1/profiles?on_conflict=id`), { method: "POST" });
  mock.timers.tick(250);
  assert.equal(avisos().length, 1);
});

test("gravação recusada pelo banco não avisa", async () => {
  respostaDoBanco = () => new Response('{"message":"permission denied"}', { status: 403 });
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/profiles?id=eq.1`, { method: "PATCH" });
  mock.timers.tick(1000);
  assert.equal(avisos().length, 0);
});

test("leitura e tabela de fora da vitrine não avisam", async () => {
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/profiles?id=eq.1`, { method: "GET" });
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/orders?id=eq.1`, { method: "PATCH" });
  mock.timers.tick(1000);
  assert.equal(avisos().length, 0);
});

test("falha de rede passa adiante do jeito que veio", async () => {
  globalThis.fetch = (async () => {
    throw new TypeError("Failed to fetch");
  }) as typeof fetch;
  await assert.rejects(fetchQueAvisaALoja(`${BANCO}/rest/v1/profiles`, { method: "PATCH" }), /Failed to fetch/);
});

test("avisar agora não duplica o aviso que estava na fila", async () => {
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/profiles?id=eq.1`, { method: "PATCH" });
  assert.equal(await avisarALojaAgora(), true);
  mock.timers.tick(1000);
  assert.equal(avisos().length, 1);
});

test("no servidor (sem janela) o fetch não agenda nada", async () => {
  delete mundo.window;
  await fetchQueAvisaALoja(`${BANCO}/rest/v1/profiles?id=eq.1`, { method: "PATCH" });
  mock.timers.tick(1000);
  assert.equal(avisos().length, 0);
  assert.equal(await avisarALojaAgora(), false);
});
