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

import { EnvioEmPartes } from "./cliente/gravador.ts";
import { lerEvento, montarEvento } from "./evento.ts";
import { capacidadeResolvida, iniciais, lerOferta, momentoDaSala, noEnderecoDaLoja, nomeDaTrilha, nomeDeTrilhaValido, podePublicar, textoDaMensagem } from "./regras.ts";
import { acaoDaLive, linkDoGoogleAgenda, motivoParaNaoVender, type SalaParaVenda } from "./venda.ts";

const SALA = { status: "agendada" as const, comeca_em: "2026-10-16T23:00:00.000Z", duracao_min: 90, espera_min: 15, participacao: "mao" as const, cameras_alunos: true };
const hora = (iso: string) => new Date(iso).getTime();

test("momento da sala: cedo, espera, ao vivo, encerrada", () => {
  assert.equal(momentoDaSala(SALA, hora("2026-10-16T22:00:00.000Z")), "cedo");
  assert.equal(momentoDaSala(SALA, hora("2026-10-16T22:50:00.000Z")), "espera");
  assert.equal(momentoDaSala(SALA, hora("2026-10-17T00:30:00.000Z")), "espera");
  /* Passou muito do horário e ninguém entrou ao vivo: fecha. */
  assert.equal(momentoDaSala(SALA, hora("2026-10-17T06:00:00.000Z")), "encerrada");
  assert.equal(momentoDaSala({ ...SALA, status: "ao_vivo" }, hora("2026-10-20T00:00:00.000Z")), "ao_vivo");
  assert.equal(momentoDaSala({ ...SALA, espera_min: 0 }, hora("2026-10-16T22:59:00.000Z")), "cedo");
});

test("quem pode publicar cada trilha", () => {
  assert.equal(podePublicar("apresentador", "tela", SALA, false), true);
  assert.equal(podePublicar("aluno", "tela", SALA, true), false);
  assert.equal(podePublicar("aluno", "mic", SALA, false), false);
  assert.equal(podePublicar("aluno", "mic", SALA, true), true);
  assert.equal(podePublicar("aluno", "cam", SALA, false), true);
  assert.equal(podePublicar("aluno", "cam", { ...SALA, cameras_alunos: false }, false), false);
  assert.equal(podePublicar("aluno", "cam", { ...SALA, cameras_alunos: false }, true), true);
  assert.equal(podePublicar("visitante", "mic", { ...SALA, participacao: "so_apresentador" }, true), false);
});

test("nome da trilha é único e conferível", () => {
  const nome = nomeDaTrilha("tela-audio", 1760000000000);
  assert.ok(nomeDeTrilhaValido(nome));
  assert.notEqual(nomeDaTrilha("cam"), nomeDaTrilha("cam"));
  assert.equal(nomeDeTrilhaValido("cam-../x"), false);
});

test("oferta: só https ou caminho da loja; prazo vencido some", () => {
  const o = lerOferta({ id: "i123", tipo: "produto", titulo: "Kit", destino: "/p/abc", preco: "189.9" });
  assert.equal(o?.destino, "/p/abc");
  assert.equal(o?.preco, 189.9);
  assert.equal(noEnderecoDaLoja(o!, "aurora").destino, "/aurora/p/abc");
  assert.equal(lerOferta({ id: "i123", tipo: "link", titulo: "x", destino: "http://a.com" })?.destino, null);
  assert.equal(lerOferta({ id: "<script>", tipo: "link", titulo: "x" }), null);
});

test("texto do bate-papo e iniciais", () => {
  assert.equal(textoDaMensagem("   "), null);
  assert.equal(textoDaMensagem("oi\n\n\n\ntudo"), "oi\n\ntudo");
  assert.equal(textoDaMensagem("a".repeat(900))?.length, 500);
  assert.equal(iniciais("Paula Reis Souza"), "PS");
  assert.equal(iniciais("joão"), "J");
});

test("evento assinado: recusa outra sala e aviso velho", () => {
  const agora = 1_800_000_000_000;
  const texto = montarEvento({ t: "msg-fora", id: "x", sala: "s1" }, agora);
  assert.equal(lerEvento(texto, "s1", agora + 1000)?.t, "msg-fora");
  assert.equal(lerEvento(texto, "s2", agora), null);
  assert.equal(lerEvento(texto, "s1", agora + 10 * 60 * 1000), null);
  assert.equal(lerEvento("{quebrado", "s1", agora), null);
  assert.equal(lerEvento(JSON.stringify({ t: "apagar-tudo", sala: "s1", ts: agora }), "s1", agora), null);
});

const VENDA: SalaParaVenda = { acesso: "paga", vender_durante: true, limite_vagas: null, gravar: true, gravacao_para: "compradores" };

test("venda da live: vagas, durante, depois, cancelada", () => {
  assert.equal(motivoParaNaoVender(VENDA, "cedo", 10), null);
  assert.match(motivoParaNaoVender({ ...VENDA, limite_vagas: 10 }, "cedo", 10) ?? "", /esgotaram/);
  assert.match(motivoParaNaoVender({ ...VENDA, vender_durante: false }, "ao_vivo", 0) ?? "", /começou/);
  /* Depois: só vende se a gravação fica para quem comprou. */
  assert.equal(motivoParaNaoVender(VENDA, "encerrada", 0), null);
  assert.match(motivoParaNaoVender({ ...VENDA, gravar: false }, "encerrada", 0) ?? "", /aconteceu/);
  assert.match(motivoParaNaoVender(VENDA, "cancelada", 0) ?? "", /cancelada/);
});

test("o botão da página da live", () => {
  const comprar = { tipo: "comprar" as const, href: "/aurora/comprar/p1" };
  assert.deepEqual(acaoDaLive({ sala: VENDA, momento: "cedo", vagas: 0, jaGarantiu: false, linkDaSala: "/aurora/live/s1", acaoDaLoja: comprar }), {
    tipo: "garantir",
    href: "/aurora/comprar/p1",
    rotulo: "Garantir minha vaga",
  });
  assert.equal(acaoDaLive({ sala: { ...VENDA, acesso: "cadastro" }, momento: "cedo", vagas: 0, jaGarantiu: false, linkDaSala: "x", acaoDaLoja: comprar }).tipo, "garantir");
  const ja = acaoDaLive({ sala: VENDA, momento: "ao_vivo", vagas: 0, jaGarantiu: true, linkDaSala: "/aurora/live/s1", acaoDaLoja: comprar });
  assert.equal(ja.tipo, "entrar");
  const aberta = acaoDaLive({ sala: { ...VENDA, acesso: "aberta" }, momento: "espera", vagas: 0, jaGarantiu: false, linkDaSala: "/aurora/live/s1", acaoDaLoja: comprar });
  assert.equal(aberta.tipo === "entrar" && aberta.rotulo, "Entrar na sala de espera");
  assert.equal(acaoDaLive({ sala: VENDA, momento: "encerrada", vagas: 0, jaGarantiu: false, linkDaSala: "x", acaoDaLoja: comprar }).tipo, "garantir");
  assert.equal(acaoDaLive({ sala: VENDA, momento: "cedo", vagas: 0, jaGarantiu: false, linkDaSala: "x", acaoDaLoja: { tipo: "fechado", motivo: null } }).tipo, "fechado");
});

test("link do Google Agenda com início e fim em UTC", () => {
  const link = linkDoGoogleAgenda({ titulo: "Live", comecaEm: "2026-10-16T23:00:00.000Z", duracaoMin: 90, detalhes: "x", local: "Loja" });
  assert.match(link, /dates=20261016T230000Z%2F20261017T003000Z/);
});

test("gravação em partes: o começo do arquivo sobe por último, o resto em ordem", async () => {
  const enviadas: Array<{ numero: number; tamanho: number; primeiro: number }> = [];
  const envio = new EnvioEmPartes(async (numero, dados) => {
    enviadas.push({ numero, tamanho: dados.length, primeiro: dados[0] });
  }, 10);
  /* Escreve 45 bytes em pedaços, e no fim corrige o começo (como o MP4 faz). */
  for (let i = 0; i < 45; i += 5) envio.escrever(new Uint8Array(5).fill(i), i);
  envio.escrever(new Uint8Array([99]), 0);
  await envio.terminar();
  assert.deepEqual(
    enviadas.map((e) => e.numero),
    [2, 3, 4, 5, 1],
  );
  assert.equal(enviadas.find((e) => e.numero === 5)?.tamanho, 5);
  assert.equal(enviadas.find((e) => e.numero === 1)?.primeiro, 99);
  assert.equal(envio.erro, null);
});

test("capacidade: cortesia sem limite, cortesia com número, pacote, grátis e limite velho", () => {
  const base = { cortesia: null, limiteDaSala: null, pessoasDoPacote: null, gratisPessoas: 30 };
  /* Cortesia sem limite: vale antes de ir ao ar e por cima de um limite velho. */
  assert.equal(capacidadeResolvida({ ...base, cortesia: { pessoas: null } }), Infinity);
  assert.equal(capacidadeResolvida({ ...base, cortesia: { pessoas: null }, limiteDaSala: 100 }), Infinity);
  assert.ok(5000 < capacidadeResolvida({ ...base, cortesia: { pessoas: null } }));
  /* Cortesia com número: pelo menos esse número; limite velho menor não diminui. */
  assert.equal(capacidadeResolvida({ ...base, cortesia: { pessoas: 100 } }), 100);
  assert.equal(capacidadeResolvida({ ...base, cortesia: { pessoas: 100 }, limiteDaSala: 30 }), 100);
  assert.equal(capacidadeResolvida({ ...base, cortesia: { pessoas: 100 }, pessoasDoPacote: 300 }), 300);
  /* Sem cortesia: limite da sala → pacote → grátis → 30. */
  assert.equal(capacidadeResolvida({ ...base, limiteDaSala: 80, pessoasDoPacote: 200 }), 80);
  assert.equal(capacidadeResolvida({ ...base, pessoasDoPacote: 200 }), 200);
  assert.equal(capacidadeResolvida(base), 30);
  assert.equal(capacidadeResolvida({ ...base, gratisPessoas: 45 }), 45);
  assert.equal(capacidadeResolvida({ ...base, gratisPessoas: null }), 30);
  assert.equal(capacidadeResolvida({ ...base, limiteDaSala: 0, pessoasDoPacote: -1, gratisPessoas: Number.NaN }), 30);
});
