import { test } from "node:test";
import assert from "node:assert/strict";
import { calcularPreco, margemDaFaixa, quantidadeCobravel, type RegraDePreco } from "./motor.ts";

const REGRA: RegraDePreco = {
  marginTargetPct: 50,
  marginMinPct: 35,
  rounding: { type: "NONE" },
};

test("os custos variáveis entram no divisor, não somados no fim", () => {
  /* Custo 100, margem 50%, variáveis 20%.
     Errado: 100/(1-0,50) = 200, e aí os 20% de 200 comem 40 → sobra 60, não 100.
     Certo:  100/(1-0,50-0,20) = 333,33 → variáveis 66,67, margem 166,67. */
  const r = calcularPreco({
    custoUnitario: 100, quantidade: 1, modo: "unidade",
    regra: REGRA, custosVariaveisPct: 20,
  });
  assert.equal(r.precoTotal, 333.33);
  assert.equal(r.margemPct, 50);
});

test("a margem prometida é a margem entregue", () => {
  /* O ponto da etapa: configurou 50%, recebe 50% — depois de imposto, taxa e
     comissão. Antes, os mesmos 50% viravam 30%. */
  for (const variaveis of [0, 5, 12.5, 20, 30]) {
    const r = calcularPreco({
      custoUnitario: 80, quantidade: 3, modo: "unidade",
      regra: REGRA, custosVariaveisPct: variaveis,
    });
    assert.ok(Math.abs(r.margemPct - 50) < 0.05, `com ${variaveis}% de variáveis a margem saiu ${r.margemPct}%`);
  }
});

test("sem custos variáveis a conta é a de sempre", () => {
  /* Compatibilidade com quem ainda não configurou nada. */
  const r = calcularPreco({ custoUnitario: 100, quantidade: 1, modo: "unidade", regra: REGRA });
  assert.equal(r.precoTotal, 200);
  assert.equal(r.margemPct, 50);
});

test("modo m² multiplica área pela quantidade", () => {
  /* 2m x 3m = 6 m², vezes 4 peças = 24 m². É onde mais se erra na mão. */
  assert.equal(
    quantidadeCobravel({ custoUnitario: 0, quantidade: 4, modo: "m2", larguraM: 2, alturaM: 3, regra: REGRA }),
    24
  );
  const r = calcularPreco({
    custoUnitario: 25, quantidade: 4, modo: "m2", larguraM: 2, alturaM: 3,
    regra: REGRA, custosVariaveisPct: 0,
  });
  assert.equal(r.custoTotal, 600);
  assert.equal(r.precoTotal, 1200);
  assert.equal(r.precoUnitario, 50);
});

test("m² sem medida não vira cobrança por peça", () => {
  /* Devolver a quantidade crua seria cobrar 4 pecas fingindo que sao 4 m². */
  const r = calcularPreco({
    custoUnitario: 25, quantidade: 4, modo: "m2", larguraM: 0, alturaM: 3, regra: REGRA,
  });
  assert.equal(r.possivel, false);
  assert.match(r.avisos.join(" "), /largura e altura/i);
});

test("faixa de quantidade: a maior que serve é a que vale", () => {
  const regra: RegraDePreco = {
    ...REGRA,
    qtyTiers: [
      { minQty: 1, maxQty: 99, marginTargetPct: 60 },
      { minQty: 100, maxQty: 499, marginTargetPct: 45 },
      { minQty: 500, marginTargetPct: 30 },
    ],
  };
  assert.equal(margemDaFaixa(regra, 50), 60);
  assert.equal(margemDaFaixa(regra, 100), 45);
  assert.equal(margemDaFaixa(regra, 499), 45);
  /* 500 tem que cair na faixa de 500, e nao na de 100 que nao tem teto. */
  assert.equal(margemDaFaixa(regra, 500), 30);
  assert.equal(margemDaFaixa(regra, 10000), 30);
});

test("quantidade fora de qualquer faixa usa a margem padrão", () => {
  const regra: RegraDePreco = {
    ...REGRA,
    qtyTiers: [{ minQty: 100, maxQty: 499, marginTargetPct: 45 }],
  };
  assert.equal(margemDaFaixa(regra, 10), 50);
});

test("margem mais variáveis somando 100% é recusado com explicação", () => {
  /* Cada real cobrado a mais gera mais imposto e mais comissão: a conta nunca
     fecha, e devolver um número gigante seria pior do que recusar. */
  const r = calcularPreco({
    custoUnitario: 100, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, marginTargetPct: 80 }, custosVariaveisPct: 25,
  });
  assert.equal(r.possivel, false);
  assert.match(r.avisos.join(" "), /não deixam espaço/i);
});

test("sem custo não inventa preço", () => {
  const r = calcularPreco({ custoUnitario: 0, quantidade: 10, modo: "unidade", regra: REGRA });
  assert.equal(r.possivel, false);
  assert.match(r.avisos.join(" "), /margem seria sobre o nada/i);
});

test("arredondamento para terminar em 90 nunca desce abaixo do bruto", () => {
  /* Descer cortaria justamente a margem que a regra existe para proteger. */
  const r = calcularPreco({
    custoUnitario: 100, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, rounding: { type: "END_WITH", cents: 90 } },
  });
  assert.equal(r.precoTotal, 200.9);
  assert.ok(r.precoTotal >= 200);
});

test("arredondamento por múltiplo sempre sobe", () => {
  const r = calcularPreco({
    custoUnitario: 100, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, rounding: { type: "MULTIPLE_OF", step: 5 } },
  });
  assert.equal(r.precoTotal, 200);

  const r2 = calcularPreco({
    custoUnitario: 101, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, rounding: { type: "MULTIPLE_OF", step: 5 } },
  });
  assert.equal(r2.precoTotal, 205);
});

test("a composição diz para onde vai cada real e fecha em 100%", () => {
  const r = calcularPreco({
    custoUnitario: 40, quantidade: 10, modo: "unidade",
    regra: REGRA, custosVariaveisPct: 18,
  });
  const soma = r.composicao.custoPct + r.composicao.variaveisPct + r.composicao.margemPct;
  assert.ok(Math.abs(soma - 100) < 0.1, `a composição somou ${soma}%`);
});

test("margem acima do teto é limitada com aviso, não silenciosamente", () => {
  const r = calcularPreco({
    custoUnitario: 10, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, marginTargetPct: 99 },
  });
  assert.match(r.avisos.join(" "), /limitada a 95%/);
  assert.equal(r.possivel, true);
});

test("margem abaixo do mínimo avisa mesmo com preço possível", () => {
  const r = calcularPreco({
    custoUnitario: 100, quantidade: 1, modo: "unidade",
    regra: { ...REGRA, marginTargetPct: 20, marginMinPct: 35 },
    custosVariaveisPct: 10,
  });
  assert.equal(r.possivel, true);
  assert.match(r.avisos.join(" "), /abaixo do mínimo/i);
});

test("os fixos pelo tempo somam no que o preço cobre, não no divisor", () => {
  /* Custo 16,80, fixo absorvido 4,20 (a ficha ocupa horas da estrutura),
     14,5% de variaveis, 50% de margem:
     P = (16,80 + 4,20) / (1 − 0,145 − 0,50) = 21 / 0,355 = 59,15. A margem
     prometida continua a entregue, e o fixo aparece como fatia própria. */
  const r = calcularPreco({
    custoUnitario: 16.8, quantidade: 1, modo: "unidade",
    regra: REGRA,
    custosVariaveisPct: 14.5,
    custoFixoUnitario: 4.2,
  });
  assert.equal(r.precoTotal, 59.15);
  assert.ok(Math.abs(r.margemPct - 50) < 0.05, `a margem saiu ${r.margemPct}%`);
  const fixosEsperado = Math.round((4.2 / 59.15) * 10000) / 100;
  assert.equal(r.composicao.fixosPct, fixosEsperado);
});

test("fixo pelo tempo é proporcional: dobro de unidades, dobro de fixo", () => {
  /* O defeito do rateio percentual era cobrar a mesma fatia de qualquer peça.
     Por tempo, quem ocupa mais horas carrega mais fixo — e a composição fecha
     em 100% com a fatia presente. */
  const r = calcularPreco({
    custoUnitario: 10, quantidade: 4, modo: "unidade",
    regra: REGRA, custosVariaveisPct: 10, custoFixoUnitario: 2.5,
  });
  assert.equal(r.custoTotal, 40);
  /* (40 + 10) / (1 − 0,10 − 0,50) = 125 */
  assert.equal(r.precoTotal, 125);
  const soma = r.composicao.custoPct + r.composicao.variaveisPct +
    r.composicao.fixosPct + r.composicao.margemPct;
  assert.ok(Math.abs(soma - 100) < 0.1, `a composição somou ${soma}%`);
});

test("sem horas configuradas nada muda: fatia zero e o preço de sempre", () => {
  const r = calcularPreco({
    custoUnitario: 47.01, quantidade: 1, modo: "unidade",
    regra: { marginTargetPct: 50, marginMinPct: 35, rounding: { type: "END_WITH", cents: 90 } },
    custosVariaveisPct: 14.5,
  });
  assert.equal(r.precoTotal, 132.9);
  assert.equal(r.composicao.fixosPct, 0);
});
