import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { analisarPixelsDaLogo, coresDaMarca, familiaDaFonte, folhaDoGoogle, logosDaLoja } from "./marca.ts";
import { ENDERECO_DO_SELO, montarCatalogo } from "./modelo.ts";
import { anoEmSaoPaulo, lerOpcoes, limiteDeProdutos, opcoesPadrao, type OpcoesDoCatalogo } from "./opcoes.ts";
import { contarPaginas } from "./paginas.ts";
import {
  agruparPorCategoria,
  destaquesDaCapa,
  instagramDaLoja,
  montarProduto,
  textoPuro,
  whatsappDaLoja,
  type LinhaDaCategoria,
  type LinhaDoProduto,
  type LojaDoCatalogo,
  type ProdutoDoCatalogo,
} from "./produtos.ts";

/* ------------------------------------------------------------ gêmeos */

test("as regras de cor e de variação são as mesmas da loja (arquivos gêmeos)", () => {
  /* O catálogo decide cor e variação com a MESMA regra da vitrine. Os dois
     apps não compartilham pacote, então as cópias têm de ser idênticas: se
     alguém mudar a da loja, este teste lembra de trazer a mudança. */
  const aqui = (nome: string) => readFileSync(new URL(`./${nome}`, import.meta.url), "utf8");
  const naLoja = (nome: string) => readFileSync(new URL(`../../../../public/src/lib/${nome}`, import.meta.url), "utf8");
  assert.equal(aqui("contraste.ts"), naLoja("color-contrast.ts"));
  assert.equal(aqui("variacoes-da-loja.ts"), naLoja("variacoes-da-loja.ts"));
});

/* ------------------------------------------------------------ opções */

test("sem escolha nenhuma, o catálogo é o que foi aprovado", () => {
  const opcoes = lerOpcoes(null, 2026);
  assert.deepEqual(opcoes, opcoesPadrao(2026));
  assert.equal(opcoes.formato, "celular");
  assert.equal(opcoes.layout, "vitrine");
  assert.equal(opcoes.botao, "loja");
  assert.equal(opcoes.titulo, "Catálogo 2026");
  assert.equal(opcoes.mostrarPreco && opcoes.mostrarEscada && opcoes.indice && opcoes.divisores, true);
});

test("o que vem do navegador fora das escolhas volta para o padrão", () => {
  const opcoes = lerOpcoes({ formato: "carta", layout: 3, botao: "comprar", mostrarPreco: "nao", titulo: 42 }, 2026);
  assert.equal(opcoes.formato, "celular");
  assert.equal(opcoes.layout, "vitrine");
  assert.equal(opcoes.botao, "loja");
  assert.equal(opcoes.mostrarPreco, true);
  assert.equal(opcoes.titulo, "42");
});

test("texto da capa perde quebra de linha e tem tamanho máximo", () => {
  const opcoes = lerOpcoes({ titulo: "  Linha\num\tdois  ", subtitulo: "x".repeat(300) }, 2026);
  assert.equal(opcoes.titulo, "Linha um dois");
  assert.equal(opcoes.subtitulo.length, 90);
  assert.equal(lerOpcoes({ titulo: "   " }, 2026).titulo, "Catálogo 2026");
});

test("categorias: lista vazia vale a loja inteira, repetida vira uma só", () => {
  assert.equal(lerOpcoes({ categorias: [] }, 2026).categorias, null);
  assert.deepEqual(lerOpcoes({ categorias: ["a", "a", " b ", ""] }, 2026).categorias, ["a", "b"]);
});

test("destaque só existe no celular, e cabe menos produto que a vitrine", () => {
  assert.equal(limiteDeProdutos({ formato: "celular", layout: "destaque" }), 80);
  assert.equal(limiteDeProdutos({ formato: "celular", layout: "vitrine" }), 300);
  assert.equal(limiteDeProdutos({ formato: "a4", layout: "destaque" }), 300);
});

test("o ano do título é o de São Paulo, não o do relógio em UTC da VPS", () => {
  /* 31/12 às 22h em São Paulo já é 1/1 em UTC. */
  assert.equal(anoEmSaoPaulo(new Date("2027-01-01T01:00:00Z")), 2026);
});

/* ------------------------------------------------------------ marca */

function logo(pixels: [number, number, number, number][], largura: number, altura: number) {
  return analisarPixelsDaLogo(Uint8Array.from(pixels.flat()), largura, altura);
}

test("logo branca em fundo transparente é clara; com fundo próprio é chapada", () => {
  const branca = logo([...Array(50).fill([255, 255, 255, 255]), ...Array(50).fill([0, 0, 0, 0])], 10, 10);
  assert.equal(branca.tom, "clara");
  const jpg = logo(Array(100).fill([240, 90, 20, 255]), 10, 10);
  assert.equal(jpg.tom, "chapada");
  assert.equal(jpg.forte !== null, true);
});

test("sem cor escolhida (ou com a do sistema), a cor da marca sai da logo", () => {
  const laranja = logo([...Array(60).fill([242, 107, 29, 255]), ...Array(40).fill([0, 0, 0, 0])], 10, 10);
  assert.equal(coresDaMarca(null, laranja).marca, laranja.forte);
  assert.equal(coresDaMarca("#4F46E5", laranja).marca, laranja.forte);
  assert.equal(coresDaMarca("#9400BD", laranja).marca, "#9400BD");
});

test("os pares de cor têm contraste de leitura", () => {
  const cores = coresDaMarca("#F26B1D", null);
  /* A tinta é a marca bem escura, nunca preto chapado. */
  assert.notEqual(cores.tinta, "#000000");
  assert.equal(cores.sobreTinta, "#FFFFFF");
});

test("logo clara sobre fundo claro ganha placa escura; sem logo, o nome escrito", () => {
  const cores = coresDaMarca("#9400BD", null);
  const branca = logo([...Array(50).fill([255, 255, 255, 255]), ...Array(50).fill([0, 0, 0, 0])], 10, 10);
  assert.equal(logosDaLoja("data:logo", branca, cores).noClaro?.placa, "tinta");
  assert.equal(logosDaLoja("data:logo", branca, cores).placeholder.modo, "silhueta");
  assert.equal(logosDaLoja(null, null, cores).noClaro, null);
});

test("a chave da fonte vira o nome da família no Google, sigla com a caixa certa", () => {
  assert.equal(familiaDaFonte("plus-jakarta-sans"), "Plus Jakarta Sans");
  assert.equal(familiaDaFonte("unbounded"), "Unbounded");
  assert.equal(familiaDaFonte("dm-sans"), "DM Sans");
  assert.equal(familiaDaFonte("source-serif-4"), "Source Serif 4");
  assert.equal(familiaDaFonte(""), null);
  assert.equal(familiaDaFonte("<script>"), null);
  assert.equal(folhaDoGoogle("Plus Jakarta Sans", "400;700"), "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;700&display=swap");
  assert.equal(folhaDoGoogle("Anton", null), "https://fonts.googleapis.com/css2?family=Anton&display=swap");
});

/* ------------------------------------------------------------ produtos */

const semZap = null;
const zap = whatsappDaLoja("(81) 99876-5432");

function linha(parcial: Partial<LinhaDoProduto> & { id: string }): LinhaDoProduto {
  return {
    name: "Produto",
    description: null,
    price: 10,
    image_url: null,
    unit_type: "unit",
    price_on_request: false,
    min_sale_qty_enabled: false,
    min_sale_qty: null,
    category_id: null,
    product_attributes: [],
    product_price_tiers: [],
    product_images: [],
    ...parcial,
  };
}

const urlDoProduto = (id: string) => `https://ktalogu.com.br/aurora/p/${id}`;

test("whatsapp: número brasileiro ganha o 55 e o rótulo com DDD", () => {
  assert.deepEqual(whatsappDaLoja("81998765432"), { numero: "5581998765432", rotulo: "(81) 99876-5432", link: "https://wa.me/5581998765432" });
  assert.equal(whatsappDaLoja("+55 (87) 3838-1234")?.rotulo, "(87) 3838-1234");
  assert.equal(whatsappDaLoja("123"), null);
  assert.equal(whatsappDaLoja(""), null);
});

test("instagram aceita @, endereço completo ou só o nome", () => {
  assert.equal(instagramDaLoja("https://www.instagram.com/graficaaurora/?hl=pt")?.rotulo, "@graficaaurora");
  assert.equal(instagramDaLoja("@metraimpressao")?.link, "https://instagram.com/metraimpressao");
  assert.equal(instagramDaLoja("   "), null);
});

test("descrição em HTML vira texto de uma linha, cortada com reticências", () => {
  assert.equal(textoPuro("<p>Caneta &amp; lápis</p><p>com&nbsp;gravação</p>", 100), "Caneta & lápis com gravação");
  assert.equal(textoPuro("a".repeat(20), 10), "aaaaaaaaaa…");
});

test("o preço inicial é o menor degrau, e a quantidade dele vai para o rótulo", () => {
  const p = montarProduto(
    linha({
      id: "p1",
      product_price_tiers: [
        { min_qty: 1, max_qty: 9, unit_price: 59.9 },
        { min_qty: 50, max_qty: null, unit_price: 42.9 },
        { min_qty: 10, max_qty: 49, unit_price: 49.9 },
        { min_qty: 100, max_qty: null, unit_price: 40, active: false },
      ],
    }),
    { urlDoProduto, whatsapp: zap, categoriaId: "c1" }
  );
  assert.deepEqual(p.preco, { tipo: "normal", valor: 42.9, unidade: "un", aPartirDe: true, quantidadeRef: 50 });
  assert.deepEqual(p.faixas.map((f) => f.min), [1, 10, 50]);
  assert.equal(p.economia, 28);
});

test("sob consulta não tem valor, e o mínimo de venda vira selo", () => {
  const p = montarProduto(linha({ id: "p2", price_on_request: true, min_sale_qty_enabled: true, min_sale_qty: 100 }), {
    urlDoProduto,
    whatsapp: zap,
    categoriaId: "c1",
  });
  assert.deepEqual(p.preco, { tipo: "consulta", unidade: "un" });
  assert.equal(p.minimo, 100);
});

test("produto por m² fala em m²", () => {
  const p = montarProduto(linha({ id: "p3", unit_type: "square_meter", price: 118 }), { urlDoProduto, whatsapp: zap, categoriaId: "c1" });
  assert.equal(p.preco.unidade, "m²");
});

test("o botão do WhatsApp leva o produto escrito; sem WhatsApp, leva para a loja", () => {
  const com = montarProduto(linha({ id: "p4", name: "Caneca" }), { urlDoProduto, whatsapp: zap, categoriaId: "c1" });
  assert.match(com.linkWhatsapp, /^https:\/\/wa\.me\/5581998765432\?text=/);
  assert.match(decodeURIComponent(com.linkWhatsapp), /Caneca\nhttps:\/\/ktalogu\.com\.br\/aurora\/p\/p4$/);
  const sem = montarProduto(linha({ id: "p4" }), { urlDoProduto, whatsapp: semZap, categoriaId: "c1" });
  assert.equal(sem.linkWhatsapp, "https://ktalogu.com.br/aurora/p/p4");
});

test("as fotos: principal primeiro, sem repetir", () => {
  const p = montarProduto(
    linha({
      id: "p5",
      image_url: "https://x/a.jpg",
      product_images: [
        { image_url: "https://x/b.jpg", sort_order: 2, is_primary: false },
        { image_url: "https://x/a.jpg", sort_order: 1, is_primary: true },
        { image_url: "https://x/c.jpg", sort_order: 1, is_primary: false },
      ],
    }),
    { urlDoProduto, whatsapp: zap, categoriaId: "c1" }
  );
  assert.deepEqual(p.fotosOriginais, ["https://x/a.jpg", "https://x/c.jpg", "https://x/b.jpg"]);
});

test("categorias: agrupa pela de cima, na ordem da loja, e o resto vai para Outros", () => {
  const categorias: LinhaDaCategoria[] = [
    { id: "b", name: "Brindes", parent_id: null },
    { id: "c", name: "Canetas", parent_id: "b" },
    { id: "a", name: "Adesivos", parent_id: null },
  ];
  const produtos = [
    montarProduto(linha({ id: "1", price: 5 }), { urlDoProduto, whatsapp: zap, categoriaId: "b" }),
    montarProduto(linha({ id: "2", price: 3 }), { urlDoProduto, whatsapp: zap, categoriaId: "b" }),
    montarProduto(linha({ id: "3", price: 9 }), { urlDoProduto, whatsapp: zap, categoriaId: "outros" }),
  ];
  const grupos = agruparPorCategoria(produtos, categorias);
  assert.deepEqual(
    grupos.map((g) => [g.id, g.produtos.length, g.menorPreco]),
    [
      ["b", 2, 3],
      ["outros", 1, 9],
    ]
  );
});

test("a capa pega um produto com foto de cada categoria, em rodízio", () => {
  const comFoto = (id: string, cat: string) => ({ ...montarProduto(linha({ id }), { urlDoProduto, whatsapp: zap, categoriaId: cat }), fotoCapa: `foto-${id}` });
  const grupos = [
    { id: "x", nome: "X", menorPreco: null, produtos: [comFoto("x1", "x"), comFoto("x2", "x")] },
    { id: "y", nome: "Y", menorPreco: null, produtos: [comFoto("y1", "y")] },
  ];
  assert.deepEqual(
    destaquesDaCapa(grupos).map((p) => p.id),
    ["x1", "y1", "x2"]
  );
});

/* ------------------------------------------------------------ o desenho */

function lojaDeTeste(parcial: Partial<LojaDoCatalogo> = {}, quantos = [5, 2, 1]): LojaDoCatalogo {
  const categorias = quantos.map((n, c) => ({
    id: `cat${c}`,
    nome: `Categoria ${c}`,
    menorPreco: 6.4,
    produtos: Array.from({ length: n }, (_, i): ProdutoDoCatalogo => ({
      ...montarProduto(linha({ id: `c${c}p${i}`, name: `Produto <${c}.${i}>`, price: 6.4 }), { urlDoProduto, whatsapp: zap, categoriaId: `cat${c}` }),
      fotoCartao: `ktfoto:${c}${i}`,
      fotoCapa: `ktfoto:capa${c}${i}`,
      fotoGrande: `ktfoto:g${c}${i}`,
      fotoMini: `ktfoto:m${c}${i}`,
    })),
  }));
  const cores = coresDaMarca("#F26B1D", null);
  return {
    nome: "Gráfica Aurora",
    slogan: "Do brinde ao banner",
    urlDaLoja: "https://ktalogu.com.br/aurora",
    urlCurta: "ktalogu.com.br/aurora",
    whatsapp: zap,
    instagram: instagramDaLoja("@graficaaurora"),
    endereco: "Rua das Palmeiras, 320",
    cores,
    logo: logosDaLoja(null, null, cores),
    fontes: { titulo: '"Unbounded"', texto: '"Sora"', folhas: ["https://fonts.googleapis.com/css2?family=Unbounded&display=swap"], soPesoNormal: false },
    categorias,
    totais: { produtos: quantos.reduce((a, b) => a + b, 0), categorias: categorias.length },
    destaquesDaCapa: destaquesDaCapa(categorias),
    qrSvg: "<svg></svg>",
    data: "19/09/2026",
    ...parcial,
  };
}

const padrao: OpcoesDoCatalogo = opcoesPadrao(2026);

function contar(html: string, trecho: string | RegExp) {
  return typeof trecho === "string" ? html.split(trecho).length - 1 : (html.match(trecho) || []).length;
}

test("a contracapa leva o selo Feito na Ktalogu, com link", () => {
  const { html } = montarCatalogo(lojaDeTeste(), padrao);
  assert.equal(contar(html, `<a class="feito-na" href="${ENDERECO_DO_SELO}">`), 1);
  assert.match(html, /Feito na <b>Ktalogu<\/b>/);
  /* Só na contracapa: a última página. */
  const ultima = html.slice(html.lastIndexOf('<section class="pagina'));
  assert.match(ultima, /feito-na/);
});

test("todo link do índice tem uma página com a âncora", () => {
  for (const opcoes of [padrao, { ...padrao, formato: "a4" as const }, { ...padrao, layout: "destaque" as const }]) {
    const { html } = montarCatalogo(lojaDeTeste({}, [9, 5, 2, 1]), opcoes);
    const alvos = [...html.matchAll(/href="#(cat-[^"]+)"/g)].map((achado) => achado[1]);
    assert.equal(alvos.length, 4, `índice em ${opcoes.formato}/${opcoes.layout}`);
    for (const alvo of alvos) assert.equal(contar(html, `id="${alvo}"`), 1, `${alvo} em ${opcoes.formato}/${opcoes.layout}`);
  }
});

test("o índice só entra com três categorias ou mais", () => {
  assert.doesNotMatch(montarCatalogo(lojaDeTeste({}, [5, 2]), padrao).html, /class="pagina indice"/);
  assert.match(montarCatalogo(lojaDeTeste({}, [5, 2, 1]), padrao).html, /class="pagina indice"/);
  assert.doesNotMatch(montarCatalogo(lojaDeTeste({}, [5, 2, 1]), { ...padrao, indice: false }).html, /class="pagina indice"/);
});

test("categoria pequena divide a página com a vizinha", () => {
  /* 5 + 2 + 1: a de 5 ganha abertura e duas páginas; as de 2 e 1 dividem uma. */
  const { html, paginas } = montarCatalogo(lojaDeTeste({}, [5, 2, 1]), padrao);
  assert.equal(contar(html, 'class="pagina divisor"'), 1);
  assert.equal(contar(html, "Categoria 1 · Categoria 2"), 1);
  /* capa, índice, abertura, 2 de vitrine, 1 misturada, contracapa */
  assert.equal(paginas, 7);
});

test("sem preço, não sobra R$ em lugar nenhum, nem a tabela de quantidade", () => {
  const { html } = montarCatalogo(lojaDeTeste(), { ...padrao, mostrarPreco: false });
  assert.doesNotMatch(html, /R\$/);
  assert.doesNotMatch(html, /Preços de/);
  assert.match(html, /atualizado em 19\/09\/2026/);
});

test("sem tabela de quantidade, some a escada e o selo de desconto", () => {
  const loja = lojaDeTeste();
  const produto = loja.categorias[0].produtos[0];
  produto.faixas = [
    { min: 1, max: 9, valor: 10 },
    { min: 10, max: null, valor: 8 },
  ];
  produto.economia = 20;
  assert.match(montarCatalogo(loja, { ...padrao, layout: "destaque" }).html, /Quanto mais, menos paga/);
  assert.match(montarCatalogo(loja, padrao).html, /até -20%/);
  assert.doesNotMatch(montarCatalogo(loja, { ...padrao, layout: "destaque", mostrarEscada: false }).html, /Quanto mais, menos paga|até -20%/);
  assert.doesNotMatch(montarCatalogo(loja, { ...padrao, mostrarEscada: false }).html, /até -20%/);
});

test("botão: Comprar leva ao produto; WhatsApp leva a mensagem; nenhum tira o link do cartão", () => {
  const loja = lojaDeTeste();
  assert.match(montarCatalogo(loja, padrao).html, /<a class="cartao" href="https:\/\/ktalogu\.com\.br\/aurora\/p\/c0p0"/);
  assert.match(montarCatalogo(loja, { ...padrao, botao: "whatsapp" }).html, /<a class="cartao" href="https:\/\/wa\.me\/5581998765432\?text=/);
  const semBotao = montarCatalogo(loja, { ...padrao, botao: "nenhum" }).html;
  assert.doesNotMatch(semBotao, /<a class="cartao"/);
  assert.doesNotMatch(semBotao, /class="botao/);
  assert.doesNotMatch(montarCatalogo(loja, { ...padrao, layout: "destaque", botao: "nenhum" }).html, /Comprar na loja|Pedir no WhatsApp/);
});

test("loja sem WhatsApp: rodapé e contracapa levam para a loja, nada de número vazio", () => {
  const { html } = montarCatalogo(lojaDeTeste({ whatsapp: null }), { ...padrao, layout: "destaque" });
  assert.doesNotMatch(html, /wa\.me/);
  assert.doesNotMatch(html, /Peça pelo WhatsApp/);
  assert.doesNotMatch(html, /Pedir no WhatsApp/);
  assert.match(html, /Peça pela loja <b>ktalogu\.com\.br\/aurora<\/b>/);
  assert.match(html, /Faça seu pedido direto na nossa loja\./);
});

test("nome de produto com HTML sai escapado", () => {
  const { html } = montarCatalogo(lojaDeTeste(), padrao);
  assert.match(html, /Produto &lt;0\.0&gt;/);
  assert.doesNotMatch(html, /Produto <0\.0>/);
});

test("o formato manda no tamanho da página", () => {
  assert.match(montarCatalogo(lojaDeTeste(), padrao).html, /@page \{ size: 450px 800px; margin: 0; \}/);
  assert.match(montarCatalogo(lojaDeTeste(), { ...padrao, formato: "a4" }).html, /@page \{ size: 794px 1123px; margin: 0; \}/);
  /* A4 é grade de 6, mesmo com destaque escolhido. */
  assert.doesNotMatch(montarCatalogo(lojaDeTeste(), { ...padrao, formato: "a4", layout: "destaque" }).html, /class="pagina destaque"/);
});

test("fonte de um peso só não ganha negrito inventado", () => {
  assert.doesNotMatch(montarCatalogo(lojaDeTeste(), padrao).html, /font-synthesis/);
  const loja = lojaDeTeste();
  loja.fontes = { ...loja.fontes, soPesoNormal: true };
  assert.match(montarCatalogo(loja, padrao).html, /html \{ font-synthesis: none; \}/);
});

test("a conta de páginas da tela bate com o catálogo montado", () => {
  const distribuicoes = [[5, 2, 1], [9, 5, 2, 1], [1], [4, 4, 4], [3, 3, 3, 3, 3], [12], [2, 7, 1, 1, 30], [6, 6], [1, 1, 1, 1, 1, 1, 1]];
  const variacoes: OpcoesDoCatalogo[] = [
    padrao,
    { ...padrao, layout: "destaque" },
    { ...padrao, formato: "a4" },
    { ...padrao, indice: false, divisores: false },
    { ...padrao, layout: "destaque", divisores: false },
  ];
  for (const tamanhos of distribuicoes) {
    for (const opcoes of variacoes) {
      const { paginas } = montarCatalogo(lojaDeTeste({}, tamanhos), opcoes);
      assert.equal(contarPaginas(tamanhos, opcoes), paginas, `${tamanhos.join("+")} em ${opcoes.formato}/${opcoes.layout}/${opcoes.indice}/${opcoes.divisores}`);
    }
  }
});
