import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { padraoDasCondicoes } from "./condicoes-comerciais.ts";
import {
  aplicarNosModelos,
  condicoesDoPadrao,
  lerModelos,
  LIMITE_DE_MODELOS,
  NOME_DO_PADRAO_ANTIGO,
  type ModeloDeCondicoes,
} from "./modelos-de-condicoes.ts";

/*
  Esta lista decide com que condições todo orçamento novo da empresa começa.
  Errar aqui é silencioso: a proposta sai para o cliente com o prazo errado.
  E tem um caso que não pode quebrar nunca — as cinco empresas que já tinham
  um padrão salvo antes desta tela existir.
*/

const condicoes = (mudancas: Partial<ReturnType<typeof padraoDasCondicoes>> = {}) => ({ ...padraoDasCondicoes(), ...mudancas });
const modelo = (id: string, nome: string, padrao = false): ModeloDeCondicoes => ({ id, nome, condicoes: condicoes(), padrao });

describe("lerModelos", () => {
  it("empresa sem nada fica sem modelo nenhum", () => {
    assert.deepEqual(lerModelos(null), []);
    assert.deepEqual(lerModelos([], null), []);
    assert.deepEqual(lerModelos("lixo", "lixo"), []);
  });

  it("o padrão antigo vira o primeiro modelo, já marcado", () => {
    const antigo = condicoes({ formas: ["boleto"], esquema: "parcelado" });
    const lidos = lerModelos(null, antigo);
    assert.equal(lidos.length, 1);
    assert.equal(lidos[0].nome, NOME_DO_PADRAO_ANTIGO);
    assert.equal(lidos[0].padrao, true);
    assert.deepEqual(lidos[0].condicoes.formas, ["boleto"]);
  });

  it("com lista salva, o padrão antigo é ignorado", () => {
    const lidos = lerModelos([{ id: "a", nome: "À vista", condicoes: condicoes(), padrao: true }], condicoes({ formas: ["boleto"] }));
    assert.equal(lidos.length, 1);
    assert.equal(lidos[0].nome, "À vista");
  });

  it("linha sem nome, sem id, sem condições ou repetida não entra", () => {
    const lidos = lerModelos([
      { id: "a", nome: "Bom", condicoes: condicoes(), padrao: false },
      { id: "a", nome: "Repetido", condicoes: condicoes() },
      { id: "b", nome: "", condicoes: condicoes() },
      { id: "", nome: "Sem id", condicoes: condicoes() },
      { id: "c", nome: "Sem condições", condicoes: null },
      "nada disso",
    ]);
    assert.deepEqual(lidos.map((m) => m.id), ["a"]);
  });

  it("dois padrões na lista: vale o primeiro", () => {
    const lidos = lerModelos([
      { id: "a", nome: "Um", condicoes: condicoes(), padrao: true },
      { id: "b", nome: "Dois", condicoes: condicoes(), padrao: true },
    ]);
    assert.deepEqual(lidos.map((m) => m.padrao), [true, false]);
  });

  it("a lista não passa do teto", () => {
    const muitos = Array.from({ length: LIMITE_DE_MODELOS + 5 }, (_, i) => ({ id: `m${i}`, nome: `Modelo ${i}`, condicoes: condicoes() }));
    assert.equal(lerModelos(muitos).length, LIMITE_DE_MODELOS);
  });
});

describe("condicoesDoPadrao", () => {
  it("devolve as condições do marcado, e null quando não há marcado", () => {
    const marcado = { ...modelo("b", "Faturado", true), condicoes: condicoes({ formas: ["boleto"] }) };
    assert.deepEqual(condicoesDoPadrao([modelo("a", "À vista"), marcado])?.formas, ["boleto"]);
    assert.equal(condicoesDoPadrao([modelo("a", "À vista")]), null);
    assert.equal(condicoesDoPadrao([]), null);
  });
});

describe("criar", () => {
  it("o primeiro modelo da empresa já nasce padrão", () => {
    const r = aplicarNosModelos([], { acao: "criar", id: "m1", nome: "  À vista no Pix  ", condicoes: condicoes() });
    assert.deepEqual(r.modelos?.map((m) => [m.id, m.nome, m.padrao]), [["m1", "À vista no Pix", true]]);
  });

  it("do segundo em diante não mexe no padrão, a não ser que peçam", () => {
    const base = [modelo("m1", "À vista", true)];
    const semPedir = aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "Faturado", condicoes: condicoes() });
    assert.deepEqual(semPedir.modelos?.map((m) => m.padrao), [true, false]);
    const pedindo = aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "Faturado", condicoes: condicoes(), padrao: true });
    assert.deepEqual(pedindo.modelos?.map((m) => m.padrao), [false, true]);
  });

  it("nome vazio, repetido ou condições inválidas não passam", () => {
    const base = [modelo("m1", "À vista")];
    assert.match(String(aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "   ", condicoes: condicoes() }).erro), /nome/i);
    assert.match(String(aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "  à VISTA ", condicoes: condicoes() }).erro), /já existe/i);
    assert.match(String(aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "Sem forma", condicoes: condicoes({ formas: [] }) }).erro), /forma de pagamento/i);
    assert.match(String(aplicarNosModelos(base, { acao: "criar", id: "m2", nome: "Sem nada", condicoes: null }).erro), /condições/i);
  });

  it("acima do teto, avisa em vez de guardar", () => {
    const cheia = Array.from({ length: LIMITE_DE_MODELOS }, (_, i) => modelo(`m${i}`, `Modelo ${i}`));
    const r = aplicarNosModelos(cheia, { acao: "criar", id: "novo", nome: "Mais um", condicoes: condicoes() });
    assert.match(String(r.erro), new RegExp(String(LIMITE_DE_MODELOS)));
    assert.equal(r.modelos, null);
  });

  it("nome comprido é cortado, não recusado", () => {
    const r = aplicarNosModelos([], { acao: "criar", id: "m1", nome: "N".repeat(200), condicoes: condicoes() });
    assert.equal(r.modelos?.[0].nome.length, 60);
  });
});

describe("renomear, substituir e excluir", () => {
  const base = [modelo("m1", "À vista", true), modelo("m2", "Faturado")];

  it("renomear troca só o nome", () => {
    const r = aplicarNosModelos(base, { acao: "renomear", id: "m2", nome: "Faturado 30 dias" });
    assert.deepEqual(r.modelos?.map((m) => m.nome), ["À vista", "Faturado 30 dias"]);
  });

  it("renomear para um nome que já existe não passa; manter o próprio nome passa", () => {
    assert.match(String(aplicarNosModelos(base, { acao: "renomear", id: "m2", nome: "à vista" }).erro), /já existe/i);
    assert.equal(aplicarNosModelos(base, { acao: "renomear", id: "m2", nome: "Faturado" }).erro, null);
  });

  it("substituir troca as condições e mantém nome e marca", () => {
    const r = aplicarNosModelos(base, { acao: "substituir", id: "m1", condicoes: condicoes({ formas: ["boleto"] }) });
    assert.deepEqual(r.modelos?.[0], { id: "m1", nome: "À vista", condicoes: condicoes({ formas: ["boleto"] }), padrao: true });
  });

  it("excluir o padrão deixa a empresa SEM padrão", () => {
    const r = aplicarNosModelos(base, { acao: "excluir", id: "m1" });
    assert.deepEqual(r.modelos?.map((m) => m.id), ["m2"]);
    assert.equal(condicoesDoPadrao(r.modelos ?? []), null);
  });

  it("modelo que não existe avisa em vez de mexer na lista", () => {
    for (const acao of [
      { acao: "renomear", id: "sumiu", nome: "Novo" },
      { acao: "substituir", id: "sumiu", condicoes: condicoes() },
      { acao: "excluir", id: "sumiu" },
      { acao: "padrao", id: "sumiu" },
    ] as const) {
      assert.match(String(aplicarNosModelos(base, acao).erro), /não encontrado/i, acao.acao);
    }
  });
});

describe("padrão", () => {
  const base = [modelo("m1", "À vista", true), modelo("m2", "Faturado"), modelo("m3", "Entrada")];

  it("marcar um tira a marca do outro", () => {
    const r = aplicarNosModelos(base, { acao: "padrao", id: "m3" });
    assert.deepEqual(r.modelos?.map((m) => m.padrao), [false, false, true]);
  });

  it("nulo tira o padrão: orçamento novo volta a começar do zero", () => {
    const r = aplicarNosModelos(base, { acao: "padrao", id: null });
    assert.deepEqual(r.modelos?.map((m) => m.padrao), [false, false, false]);
    assert.equal(condicoesDoPadrao(r.modelos ?? []), null);
  });
});
