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

import {
  aberturasPorHora,
  agruparAparelhos,
  estimarChance,
  faixaPreferida,
  haQuantoTempo,
  linkDoWhatsapp,
  montarChance,
  montarVisitas,
  passosDoLink,
  pontosDaConsulta,
  porcentoLegivel,
  resumir,
  separarDaEquipe,
  sinaisDoLink,
  type AberturaGuardada,
  type OrcamentoDasMetricas,
  type PontoDaCoorte,
} from "./metricas-do-link.ts";

function abertura(parcial: Partial<AberturaGuardada> & Pick<AberturaGuardada, "id" | "opened_at">): AberturaGuardada {
  return {
    viewer_key: "leitor-1",
    device_key: "iphone",
    last_seen_at: parcial.opened_at,
    active_seconds: 0,
    max_scroll: 0,
    sections: [],
    actions: [],
    device_type: "celular",
    os_name: "iOS",
    os_version: "17.5",
    browser_name: "Safari",
    device_model: "iPhone",
    in_app: null,
    referrer: null,
    city: null,
    region: null,
    country: "BR",
    is_team: false,
    counted: true,
    ...parcial,
  };
}

const orcamento: OrcamentoDasMetricas = {
  numero: 172,
  status: "draft",
  criadoEm: "2026-09-20T12:00:00.000Z",
  listaDePrecos: false,
  linkAtivo: true,
  linkVenceEm: null,
  validade: null,
  contadorAntigo: 0,
  primeiraAntiga: null,
  ultimaAntiga: null,
  pedidoEm: null,
  clienteNome: "MARIA DA SILVA",
  clienteWhatsapp: "(81) 99999-0000",
};

describe("equipe e visitas", () => {
  it("aberturas da equipe (marca do banco ou 'fui eu') saem da conta do cliente", () => {
    const { doCliente, equipe } = separarDaEquipe(
      [
        abertura({ id: "1", opened_at: "2026-09-25T12:00:00Z" }),
        abertura({ id: "2", opened_at: "2026-09-25T12:05:00Z", is_team: true }),
        abertura({ id: "3", opened_at: "2026-09-25T12:06:00Z", viewer_key: "celular-do-vendedor" }),
      ],
      ["celular-do-vendedor"]
    );
    assert.deepEqual(doCliente.map((a) => a.id), ["1"]);
    assert.deepEqual(equipe.map((a) => a.id), ["2", "3"]);
  });

  it("aberturas do mesmo aparelho com menos de 30 min entre elas são uma visita só", () => {
    const visitas = montarVisitas(
      [
        abertura({ id: "a", opened_at: "2026-09-25T12:00:00Z", last_seen_at: "2026-09-25T12:03:00Z", active_seconds: 150, max_scroll: 40, sections: ["itens"] }),
        abertura({
          id: "b",
          opened_at: "2026-09-25T12:20:00Z",
          last_seen_at: "2026-09-25T12:24:00Z",
          active_seconds: 200,
          max_scroll: 100,
          sections: ["total", "itens", "fim"],
          actions: [{ tipo: "pdf", em: "2026-09-25T12:22:00Z" }],
        }),
        abertura({ id: "c", opened_at: "2026-09-25T15:00:00Z", last_seen_at: "2026-09-25T15:01:00Z", active_seconds: 30 }),
        abertura({ id: "d", opened_at: "2026-09-25T12:10:00Z", device_key: "pc", viewer_key: "leitor-2", device_type: "computador" }),
      ],
      "2026-09-25T14:00:00Z"
    );
    assert.equal(visitas.length, 3);
    const manha = visitas.find((visita) => visita.id === "a");
    assert.ok(manha);
    assert.equal(manha.aberturas, 2);
    assert.equal(manha.segundos, 350);
    assert.equal(manha.rolagem, 100);
    assert.deepEqual(manha.secoes, ["itens", "total", "fim"]);
    assert.deepEqual(manha.acoes.map((acao) => acao.tipo), ["pdf"]);
    assert.equal(manha.fim, "2026-09-25T12:24:00.000Z");
    assert.equal(manha.depoisDoPedido, false);
    /* A mais recente primeiro, e a que veio depois do pedido marcada. */
    assert.equal(visitas[0].id, "c");
    assert.equal(visitas[0].depoisDoPedido, true);
  });

  it("dois navegadores do mesmo aparelho viram um aparelho, com o nome e o que foi usado", () => {
    const aberturas = [
      abertura({ id: "1", opened_at: "2026-09-25T12:00:00Z", viewer_key: "safari" }),
      abertura({ id: "2", opened_at: "2026-09-25T13:00:00Z", viewer_key: "whats", browser_name: null, in_app: "WhatsApp" }),
      abertura({
        id: "3",
        opened_at: "2026-09-25T14:00:00Z",
        viewer_key: "pc",
        device_key: "pc",
        device_type: "computador",
        os_name: "Windows",
        os_version: "11",
        browser_name: "Chrome",
        device_model: null,
        city: "Recife",
        region: "Pernambuco",
      }),
    ];
    const aparelhos = agruparAparelhos(aberturas, montarVisitas(aberturas));
    assert.equal(aparelhos.length, 2);
    assert.equal(aparelhos[0].nome, "iPhone");
    assert.equal(aparelhos[0].aberturas, 2);
    assert.deepEqual(aparelhos[0].leitores, ["safari", "whats"]);
    assert.equal(aparelhos[0].detalhe, "iOS 17.5 · Safari e dentro do WhatsApp");
    assert.equal(aparelhos[0].visitas, 2);
    assert.equal(aparelhos[1].nome, "Computador com Windows");
    assert.equal(aparelhos[1].lugar, "Recife, Pernambuco");
  });
});

describe("horários", () => {
  it("conta as horas no horário de Brasília", () => {
    const horas = aberturasPorHora([{ opened_at: "2026-09-25T22:30:00Z" }, { opened_at: "2026-09-26T02:10:00Z" }]);
    assert.equal(horas[19], 1);
    assert.equal(horas[23], 1);
  });

  it("a faixa preferida só aparece quando concentra metade das aberturas", () => {
    const horas = Array.from({ length: 24 }, () => 0);
    horas[19] = 2;
    horas[20] = 1;
    horas[9] = 1;
    assert.deepEqual(faixaPreferida(horas), { inicio: 19, fim: 21, aberturas: 3, total: 4 });
    const espalhadas = Array.from({ length: 24 }, () => 0);
    espalhadas[9] = 1;
    espalhadas[14] = 1;
    espalhadas[20] = 1;
    assert.equal(faixaPreferida(espalhadas), null);
    const poucas = Array.from({ length: 24 }, () => 0);
    poucas[19] = 2;
    assert.equal(faixaPreferida(poucas), null);
    const madrugada = Array.from({ length: 24 }, () => 0);
    madrugada[23] = 2;
    madrugada[0] = 2;
    assert.deepEqual(faixaPreferida(madrugada), { inicio: 23, fim: 1, aberturas: 4, total: 4 });
  });
});

describe("chance de fechar", () => {
  /* Números reais da plataforma (21/09/2026): 12 h depois da primeira
     abertura, cliente de primeira compra; a empresa com 11 casos, 1 fechado. */
  const ponto: PontoDaCoorte = {
    dias: 0.5,
    n: 741,
    ganhos: 230,
    n_estrato: 617,
    ganhos_estrato: 168,
    n_empresa: 16,
    ganhos_empresa: 3,
    n_empresa_estrato: 11,
    ganhos_empresa_estrato: 1,
  };

  it("usa o grupo certo e puxa pela empresa conforme os casos dela", () => {
    const estimativa = estimarChance(ponto);
    assert.ok(estimativa);
    assert.equal(estimativa.separouRecorrencia, true);
    assert.equal(estimativa.geral.casos, 617);
    assert.ok(Math.abs(estimativa.geral.taxa - 168 / 617) < 1e-9);
    const esperado = (1 + 20 * (168 / 617)) / (11 + 20);
    assert.ok(Math.abs(estimativa.chance - esperado) < 1e-9);
    assert.ok(Math.abs(estimativa.empresa.peso - 11 / 31) < 1e-9);
    assert.equal(estimativa.precisao, "alta");
  });

  it("grupo pequeno demais: vale a média de todos; sem casos, sem conta", () => {
    const pequeno = estimarChance({ ...ponto, n_estrato: 20, ganhos_estrato: 10 });
    assert.ok(pequeno);
    assert.equal(pequeno.separouRecorrencia, false);
    assert.equal(pequeno.geral.casos, 741);
    assert.equal(pequeno.empresa.casos, 16);
    assert.equal(estimarChance({ ...ponto, n: 0, n_estrato: 0 }), null);
    assert.equal(estimarChance({ ...ponto, n: 90, ganhos: 9, n_estrato: 0 })?.precisao, "media");
    assert.equal(estimarChance({ ...ponto, n: 40, ganhos: 4, n_estrato: 0 })?.precisao, "baixa");
  });

  it("pontos da consulta: primeira abertura, agora, amanhã, em 3 e em 7 dias", () => {
    assert.deepEqual(pontosDaConsulta(0.5, true), [0, 0.5, 1.5, 3.5, 7.5]);
    assert.deepEqual(pontosDaConsulta(2.1234, false), [2.123, 3.123, 5.123, 9.123]);
  });

  it("orçamento já decidido ou lista de preços não tem conta", () => {
    const base = { listaDePrecos: false, aberto: true, diasDesde: 1, resposta: {} };
    assert.equal(montarChance({ ...base, status: "approved", pedidoEm: "2026-09-25T12:00:00Z" }).estado, "fechado");
    assert.equal(montarChance({ ...base, status: "draft", pedidoEm: "2026-09-25T12:00:00Z" }).estado, "fechado");
    assert.equal(montarChance({ ...base, status: "rejected", pedidoEm: null }).estado, "recusado");
    /* Aprovado no painel sem pedido: ainda não fechou. */
    assert.equal(montarChance({ ...base, status: "approved", pedidoEm: null }).estado, "aprovado");
    assert.equal(montarChance({ ...base, status: "production", pedidoEm: null }).estado, "aprovado");
    assert.equal(montarChance({ ...base, status: "draft", pedidoEm: null, listaDePrecos: true }).estado, "sem-conta");
    assert.equal(montarChance({ ...base, status: "draft", pedidoEm: null, resposta: { pontos: [] } }).estado, "sem-conta");
  });

  it("monta a chance de agora, a queda nos próximos dias e a comparação dos grupos", () => {
    const pontos = [
      { ...ponto, dias: 0, n: 1245, ganhos: 734, n_estrato: 1013, ganhos_estrato: 564 },
      ponto,
      { ...ponto, dias: 1.5, n: 662, ganhos: 151, n_estrato: 557, ganhos_estrato: 108 },
      { ...ponto, dias: 3.5, n: 619, ganhos: 108, n_estrato: 526, ganhos_estrato: 77 },
      { ...ponto, dias: 7.5, n: 573, ganhos: 62, n_estrato: 492, ganhos_estrato: 43 },
    ];
    const chance = montarChance({
      status: "draft",
      pedidoEm: null,
      listaDePrecos: false,
      aberto: true,
      diasDesde: 0.5,
      resposta: { pontos, recorrente: false, coorte: 1245, com_registro_detalhado: 0 },
    });
    assert.equal(chance.estado, "aberto");
    if (chance.estado !== "aberto") return;
    assert.equal(chance.atual.dias, 0.5);
    assert.deepEqual(chance.daqui.map((item) => item.rotulo), ["Amanhã", "Em 3 dias", "Em 7 dias"]);
    assert.ok(chance.daqui[0].chance > chance.daqui[2].chance);
    assert.ok(chance.naPrimeiraAbertura && chance.naPrimeiraAbertura.chance > chance.atual.chance);
    assert.ok(chance.recorrencia);
    /* Quem já comprou (os outros 124 do ponto) fecha mais: 62 de 124. */
    assert.ok(Math.abs((chance.recorrencia.outroGrupo ?? 0) - 62 / 124) < 1e-9);
    assert.equal(chance.coorte.orcamentos, 1245);
  });
});

describe("resumo, sinais e próximos passos", () => {
  const agora = new Date("2026-09-25T18:00:00Z").getTime();

  it("junta o contador antigo com o registro detalhado sem contar duas vezes", () => {
    const doCliente = [
      abertura({ id: "1", opened_at: "2026-09-24T12:00:00Z", last_seen_at: "2026-09-24T12:05:00Z", active_seconds: 200, sections: ["total"] }),
      abertura({ id: "2", opened_at: "2026-09-25T17:59:40Z", last_seen_at: "2026-09-25T17:59:50Z", active_seconds: 10, actions: [{ tipo: "aprovar", em: "2026-09-25T17:59:45Z" }] }),
    ];
    const visitas = montarVisitas(doCliente);
    const resumo = resumir({
      doCliente,
      daEquipe: [abertura({ id: "3", opened_at: "2026-09-24T11:00:00Z", is_team: true, counted: false })],
      visitas,
      aparelhos: agruparAparelhos(doCliente, visitas),
      orcamento: { contadorAntigo: 5, primeiraAntiga: "2026-09-21T10:00:00Z", ultimaAntiga: "2026-09-25T17:59:40Z" },
      agora,
    });
    assert.equal(resumo.aberturas, 5);
    assert.deepEqual(resumo.legado, { aberturas: 3, primeira: "2026-09-21T10:00:00Z", ultima: null });
    assert.equal(resumo.primeira, "2026-09-21T10:00:00.000Z");
    assert.equal(resumo.abertoAgora, true);
    assert.equal(resumo.segundos, 210);
    assert.deepEqual(resumo.viu, ["total"]);
    assert.deepEqual(resumo.acoes, ["aprovar"]);
    assert.equal(resumo.daEquipe, 1);

    const sinais = sinaisDoLink(resumo, orcamento, agora).map((sinal) => sinal.id);
    assert.ok(sinais.includes("agora"));
    assert.ok(sinais.includes("voltou"));
    assert.ok(sinais.includes("comecou"));

    const passos = passosDoLink({ resumo, orcamento, chance: { estado: "sem-conta", motivo: "" }, ultimoEnvio: null, agora, hoje: "2026-09-25" });
    assert.deepEqual(passos.map((passo) => passo.id).slice(0, 2), ["comecou", "agora"]);
    assert.match(passos[0].mensagem ?? "", /^Oi, Maria! Tudo certo com o orçamento #172\?/);
  });

  it("link vencido vem primeiro; sem abrir depois de um dia, reenviar; validade vencida avisa", () => {
    const vazio = resumir({ doCliente: [], daEquipe: [], visitas: [], aparelhos: [], orcamento, agora });
    const passos = passosDoLink({
      resumo: vazio,
      orcamento: { ...orcamento, linkVenceEm: "2026-09-24T00:00:00Z", validade: "2026-09-24" },
      chance: { estado: "sem-conta", motivo: "" },
      ultimoEnvio: "2026-09-23T12:00:00Z",
      agora,
      hoje: "2026-09-25",
    });
    assert.deepEqual(passos.map((passo) => passo.id), ["renovar", "nao-abriu", "validade"]);
    assert.equal(passos[1].botao, "reenviar");
    /* Mandado há poucas horas: ainda não é hora de cobrar. */
    const cedo = passosDoLink({ resumo: vazio, orcamento, chance: { estado: "sem-conta", motivo: "" }, ultimoEnvio: "2026-09-25T15:00:00Z", agora, hoje: "2026-09-25" });
    assert.deepEqual(cedo.map((passo) => passo.id), []);
    /* Fechado ou recusado: nada a fazer. */
    assert.deepEqual(passosDoLink({ resumo: vazio, orcamento, chance: { estado: "fechado", em: null }, ultimoEnvio: null, agora, hoje: "2026-09-25" }), []);
    /* Aprovado sem pedido: o único passo é gerar o pedido. */
    assert.deepEqual(
      passosDoLink({ resumo: vazio, orcamento, chance: { estado: "aprovado" }, ultimoEnvio: null, agora, hoje: "2026-09-25" }).map((passo) => passo.id),
      ["gerar-pedido"]
    );
  });

  it("esfriando: dias sem abrir, com a queda da chance no texto", () => {
    const doCliente = [abertura({ id: "1", opened_at: "2026-09-20T12:00:00Z", last_seen_at: "2026-09-20T12:10:00Z", active_seconds: 300 })];
    const visitas = montarVisitas(doCliente);
    const resumo = resumir({ doCliente, daEquipe: [], visitas, aparelhos: agruparAparelhos(doCliente, visitas), orcamento, agora });
    const chance = montarChance({
      status: "draft",
      pedidoEm: null,
      listaDePrecos: false,
      aberto: true,
      diasDesde: 5.25,
      resposta: {
        pontos: [
          { dias: 5.25, n: 600, ganhos: 60, n_estrato: 500, ganhos_estrato: 45, n_empresa: 0, ganhos_empresa: 0, n_empresa_estrato: 0, ganhos_empresa_estrato: 0 },
          { dias: 12.25, n: 550, ganhos: 22, n_estrato: 460, ganhos_estrato: 16, n_empresa: 0, ganhos_empresa: 0, n_empresa_estrato: 0, ganhos_empresa_estrato: 0 },
        ],
      },
    });
    const passos = passosDoLink({ resumo, orcamento, chance, ultimoEnvio: null, agora, hoje: "2026-09-25" });
    const esfriando = passos.find((passo) => passo.id === "esfriando");
    assert.ok(esfriando);
    assert.equal(esfriando.titulo, "Faz 5 dias que ele não abre o orçamento");
    assert.match(esfriando.texto, /em uma semana, orçamentos assim chegam a 3%/);
  });

  it("tempo e porcentagem para ler de relance", () => {
    const agora = Date.parse("2026-09-25T18:00:00Z");
    assert.equal(haQuantoTempo("2026-09-25T17:59:30Z", agora), "agora há pouco");
    assert.equal(haQuantoTempo("2026-09-25T15:00:00Z", agora), "há 3 horas");
    assert.equal(haQuantoTempo("2026-09-20T18:00:00Z", agora), "há 5 dias");
    assert.equal(haQuantoTempo("2026-05-06T14:00:00Z", agora), "há 5 meses");
    assert.equal(haQuantoTempo("2024-09-01T18:00:00Z", agora), "há 2 anos");
    assert.equal(porcentoLegivel(0.3812), "38%");
    assert.equal(porcentoLegivel(0.004), "<1%");
    assert.equal(porcentoLegivel(0), "0%");
    assert.equal(porcentoLegivel(0.996), "100%");
  });

  it("WhatsApp: número brasileiro com ou sem 55, e nada sem número", () => {
    assert.equal(linkDoWhatsapp("(81) 99999-0000", "Oi"), "https://wa.me/5581999990000?text=Oi");
    assert.equal(linkDoWhatsapp("+55 81 99999-0000", "Oi"), "https://wa.me/5581999990000?text=Oi");
    assert.equal(linkDoWhatsapp("", "Oi"), null);
    assert.equal(linkDoWhatsapp("1234", "Oi"), null);
  });
});
