import { test } from "node:test";
import assert from "node:assert/strict";
import {
  explicacaoDoMinimo,
  minimoDaLinhaGravada,
  ROTULO_DO_MINIMO,
  precoDaLinha,
  rotuloDaUnidade,
  somarAdicionais,
  sufixoDoAdicional,
  textoDoMinimoAplicado,
  textoDoMinimoDoItem,
  unidadeDoAdicional,
  valorMinimoDoItem,
} from "./adicionais.ts";

const FAIXAS = [
  { min_qty: 1, max_qty: 4.99, unit_price: 68 },
  { min_qty: 5, max_qty: 14.99, unit_price: 58 },
  { min_qty: 15, max_qty: null, unit_price: 49 },
];

const centavos = (n: number) => Math.round(n * 100) / 100;

test("opção antiga sem unidade segue a regra de sempre", () => {
  assert.equal(unidadeDoAdicional({ price: 12, price_applies_per_quantity: true }, "square_meter"), "measure");
  assert.equal(unidadeDoAdicional({ price: 12 }, "square_meter"), "measure");
  assert.equal(unidadeDoAdicional({ price: 12, price_applies_per_quantity: false }, "square_meter"), "order");
  assert.equal(unidadeDoAdicional({ price: 12, price_applies_per_quantity: true }, "unit"), "piece");
  assert.equal(unidadeDoAdicional({ price: 12, price_applies_per_quantity: false }, "unit"), "order");
});

test("unidade declarada manda; 'por medida' num produto por unidade é a peça", () => {
  assert.equal(unidadeDoAdicional({ price: 12, price_unit: "piece", price_applies_per_quantity: true }, "square_meter"), "piece");
  assert.equal(unidadeDoAdicional({ price: 12, price_unit: "order", price_applies_per_quantity: true }, "square_meter"), "order");
  assert.equal(unidadeDoAdicional({ price: 12, price_unit: "measure" }, "unit"), "piece");
  assert.equal(unidadeDoAdicional({ price: 12, price_unit: "MEASURE " }, "meter"), "measure");
  assert.equal(unidadeDoAdicional({ price: 12, price_unit: "qualquer" }, "square_meter"), "measure");
});

test("soma separa por medida, por peça e uma vez", () => {
  const soma = somarAdicionais(
    [
      { price: 12, price_unit: "measure" },
      { price: 14 },
      { price: 250, price_unit: "piece" },
      { price: 180, price_unit: "order" },
      { price: 0, price_unit: "piece" },
      null,
    ],
    "square_meter"
  );
  assert.deepEqual(soma, { porMedida: 26, porPeca: 250, umaVez: 180 });
});

test("adesivo de 14,40 m²: a faixa vem da área e o adicional por m² multiplica pela área", () => {
  const conta = precoDaLinha({
    precoBase: 68,
    faixas: FAIXAS,
    pecas: 1,
    quantidadeEfetiva: 14.4,
    adicionais: { porMedida: 12, porPeca: 0, umaVez: 0 },
  });
  assert.equal(conta.precoDaFaixa, 58);
  assert.equal(conta.precoPorMedidaSemRateio, 70);
  assert.equal(centavos(conta.total), 1008);
  assert.equal(centavos(conta.precoPorPeca), 1008);
  assert.equal(centavos(conta.precoPorMedida * 14.4), 1008);
});

test("dez peças de 1 × 1 m: laminação por m² dá R$ 120, por peça dá R$ 2.500, uma vez dá R$ 180", () => {
  const base = { precoBase: 68, faixas: FAIXAS, pecas: 10, quantidadeEfetiva: 10 };
  const porMedida = precoDaLinha({ ...base, adicionais: { porMedida: 12, porPeca: 0, umaVez: 0 } });
  const porPeca = precoDaLinha({ ...base, adicionais: { porMedida: 0, porPeca: 250, umaVez: 0 } });
  const umaVez = precoDaLinha({ ...base, adicionais: { porMedida: 0, porPeca: 0, umaVez: 180 } });
  const soFaixa = 58 * 10;
  assert.equal(centavos(porMedida.total - soFaixa), 120);
  assert.equal(centavos(porPeca.total - soFaixa), 2500);
  assert.equal(centavos(umaVez.total - soFaixa), 180);
  /* O preço por medida guardado no pedido reconstrói o total pela área. */
  assert.equal(centavos(porPeca.precoPorMedida * 10), centavos(porPeca.total));
});

test("produto por unidade: por medida e por peça são a mesma coisa", () => {
  const conta = precoDaLinha({
    precoBase: 10,
    faixas: [],
    pecas: 3,
    quantidadeEfetiva: 3,
    adicionais: { porMedida: 1, porPeca: 2, umaVez: 5 },
  });
  assert.equal(conta.total, (10 + 1) * 3 + 2 * 3 + 5);
  assert.equal(centavos(conta.precoPorPeca), centavos(conta.total / 3));
});

test("preço apenas pela variação e sob consulta", () => {
  const soVariacao = precoDaLinha({
    precoBase: 68, faixas: FAIXAS, pecas: 1, quantidadeEfetiva: 2,
    adicionais: { porMedida: 30, porPeca: 0, umaVez: 0 }, soVariacao: true,
  });
  assert.equal(soVariacao.total, 60);
  const sobConsulta = precoDaLinha({ precoBase: 68, faixas: FAIXAS, pecas: 1, quantidadeEfetiva: 2, sobConsulta: true });
  assert.equal(sobConsulta.total, 0);
});

test("a linha nunca fica negativa e o zero não divide", () => {
  const conta = precoDaLinha({
    precoBase: 10, faixas: [], pecas: 0, quantidadeEfetiva: 0,
    adicionais: { porMedida: -80, porPeca: 0, umaVez: 0 },
  });
  assert.equal(conta.total, 0);
  assert.equal(Number.isFinite(conta.precoPorMedida), true);
});

test("rótulos e sufixos", () => {
  assert.equal(rotuloDaUnidade("measure", "square_meter"), "/m²");
  assert.equal(rotuloDaUnidade("piece", "square_meter"), "por peça");
  assert.equal(rotuloDaUnidade("order", "square_meter"), "no pedido");
  assert.equal(rotuloDaUnidade("measure", "unit"), "por unidade");
  assert.equal(sufixoDoAdicional({ price: 12 }, "square_meter"), "/m²");
  assert.equal(sufixoDoAdicional({ price: 12, price_unit: "piece" }, "square_meter"), " por peça");
  assert.equal(sufixoDoAdicional({ price: 12, price_applies_per_quantity: false }, "unit"), " no pedido");
  assert.equal(sufixoDoAdicional({ price: 12 }, "unit"), "");
  assert.equal(sufixoDoAdicional({ price: 12 }, "meter"), "/m");
});

/* ------------------------------------------------ valor mínimo do item */

const LONA = 125;
const centavosDe = (n: number) => Math.round(n * 100) / 100;

test("mínimo do item: lona 0,60 × 0,40 sai pelo mínimo", () => {
  const conta = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.6 * 0.4, valorMinimo: 70 });
  assert.equal(conta.total, 70);
  assert.equal(conta.minimoAplicado, 70);
  assert.equal(centavosDe(conta.totalSemMinimo), 30);
  /* O preço por medida guardado fecha a linha no mínimo, sem virar 69,99. */
  assert.equal(centavosDe(conta.precoPorMedida * 0.24), 70);
  assert.equal(centavosDe(conta.precoPorMedidaSemRateio), 125);
});

test("mínimo do item: 20 adesivos de 5 × 5 na mesma linha contam juntos", () => {
  const area = 20 * 0.05 * 0.05;
  const conta = precoDaLinha({ precoBase: LONA, pecas: 20, quantidadeEfetiva: area, valorMinimo: 70 });
  assert.equal(conta.total, 70);
  assert.equal(centavosDe(conta.totalSemMinimo), 6.25);
  assert.equal(centavosDe(conta.precoPorPeca), 3.5);
});

test("mínimo do item: 300 adesivos passam do mínimo e ficam com a conta normal", () => {
  const area = 300 * 0.05 * 0.05;
  const conta = precoDaLinha({ precoBase: LONA, pecas: 300, quantidadeEfetiva: area, valorMinimo: 70 });
  assert.equal(centavosDe(conta.total), 93.75);
  assert.equal(conta.minimoAplicado, null);
});

test("mínimo do item: duas linhas diferentes pagam o mínimo cada uma", () => {
  const lona = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.24, valorMinimo: 70 });
  const adesivo = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.0025, valorMinimo: 70 });
  assert.equal(lona.total + adesivo.total, 140);
});

test("mínimo do item: arredondamento em 0,3 m² e 0,24 m²", () => {
  for (const area of [0.3, 0.24, 0.05, 0.0025, 0.5599]) {
    const conta = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: area, valorMinimo: 70 });
    assert.equal(conta.total, 70);
    /* Gravado com 6 casas, o preço por medida ainda fecha 70,00. */
    const gravado = Math.round(conta.precoPorMedida * 1e6) / 1e6;
    assert.equal(centavosDe(gravado * area), 70);
  }
  /* 0,56 m² × 125 = 70,00: igual ao mínimo, não pega. */
  const igual = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.56, valorMinimo: 70 });
  assert.equal(igual.minimoAplicado, null);
  assert.equal(centavosDe(igual.total), 70);
});

test("mínimo do item: vem depois de faixas e adicionais", () => {
  const adicionais = { porMedida: 12, porPeca: 2, umaVez: 5 };
  /* 1 peça de 0,5 m²: faixa de 68 + 12 = 80/m² × 0,5 + 2 + 5 = 47. */
  const conta = precoDaLinha({ precoBase: 68, faixas: FAIXAS, pecas: 1, quantidadeEfetiva: 0.5, adicionais, valorMinimo: 70 });
  assert.equal(centavosDe(conta.totalSemMinimo), 47);
  assert.equal(conta.total, 70);
  /* 10 m² caem na faixa de 58: (58 + 12) × 10 + 2 + 5 = 707, acima do mínimo. */
  const grande = precoDaLinha({ precoBase: 68, faixas: FAIXAS, pecas: 1, quantidadeEfetiva: 10, adicionais, valorMinimo: 70 });
  assert.equal(centavosDe(grande.total), 707);
  assert.equal(grande.minimoAplicado, null);
});

test("mínimo do item: produto sem mínimo continua igual (regressão)", () => {
  const casos = [
    { precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.24 },
    { precoBase: 68, faixas: FAIXAS, pecas: 3, quantidadeEfetiva: 6, adicionais: { porMedida: 12, porPeca: 250, umaVez: 5 } },
    { precoBase: 10, pecas: 0, quantidadeEfetiva: 0 },
  ];
  for (const caso of casos) {
    const sem = precoDaLinha(caso);
    const comNulo = precoDaLinha({ ...caso, valorMinimo: null });
    const comZero = precoDaLinha({ ...caso, valorMinimo: 0 });
    for (const outra of [comNulo, comZero]) {
      assert.equal(outra.total, sem.total);
      assert.equal(outra.precoPorMedida, sem.precoPorMedida);
      assert.equal(outra.precoPorPeca, sem.precoPorPeca);
      assert.equal(outra.minimoAplicado, null);
    }
    assert.equal(sem.totalSemMinimo, sem.total);
  }
});

test("mínimo do item: sob consulta não tem efeito", () => {
  const conta = precoDaLinha({ precoBase: LONA, pecas: 1, quantidadeEfetiva: 0.24, sobConsulta: true, valorMinimo: 70 });
  assert.equal(conta.total, 0);
  assert.equal(conta.minimoAplicado, null);
});

test("mínimo do item: onde vale (unidade, canal e chaves)", () => {
  const produto = { unit_type: "square_meter", valor_minimo_item: "70", valor_minimo_orcamentos: true, valor_minimo_loja: false, valor_minimo_pdv: null };
  assert.equal(valorMinimoDoItem(produto, "orcamentos"), 70);
  assert.equal(valorMinimoDoItem(produto, "loja"), null);
  /* Chave sem valor (leitura antiga) conta como ligada. */
  assert.equal(valorMinimoDoItem(produto, "pdv"), 70);
  assert.equal(valorMinimoDoItem({ ...produto, unit_type: "meter" }, "orcamentos"), 70);
  assert.equal(valorMinimoDoItem({ ...produto, unit_type: "linear_meter" }, "orcamentos"), 70);
  assert.equal(valorMinimoDoItem({ ...produto, unit_type: "unit" }, "orcamentos"), null);
  assert.equal(valorMinimoDoItem({ ...produto, price_on_request: true }, "orcamentos"), null);
  assert.equal(valorMinimoDoItem({ ...produto, valor_minimo_item: null }, "orcamentos"), null);
  assert.equal(valorMinimoDoItem({ ...produto, valor_minimo_item: 0 }, "orcamentos"), null);
  assert.equal(valorMinimoDoItem(null, "orcamentos"), null);
});

test("mínimo do item: textos do aviso e leitura da linha gravada", () => {
  const nbsp = (texto: string) => texto.replace(/ /g, " ");
  assert.equal(nbsp(textoDoMinimoAplicado(70, 30)), "Valor mínimo aplicado: R$ 70,00 (o tamanho escolhido daria R$ 30,00)");
  assert.equal(nbsp(textoDoMinimoAplicado(70, null)), "Valor mínimo aplicado: R$ 70,00");
  assert.equal(nbsp(textoDoMinimoDoItem(70)), "Valor mínimo deste item: R$ 70,00");
  assert.equal(ROTULO_DO_MINIMO, "Valor mínimo do item");
  const explicacao = nbsp(explicacaoDoMinimo(70));
  assert.ok(explicacao.includes("valor mínimo de R$ 70,00"));
  assert.ok(!/daria|pedido mínimo/.test(explicacao));
  assert.deepEqual(minimoDaLinhaGravada({ valor_minimo_aplicado: "70.00", valor_sem_minimo: "30.00" }), { minimo: 70, semMinimo: 30 });
  assert.deepEqual(minimoDaLinhaGravada({ valor_minimo_aplicado: 70, valor_sem_minimo: null }), { minimo: 70, semMinimo: null });
  assert.equal(minimoDaLinhaGravada({ valor_minimo_aplicado: null, valor_sem_minimo: 30 }), null);
  assert.equal(minimoDaLinhaGravada(null), null);
});
