/* eslint-disable @typescript-eslint/no-explicit-any -- os testes leem o payload da Spedy em profundidade */
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import {
  CAMPINAS,
  codigosDoServico,
  destinatarioParaNota,
  documentoValido,
  gtinValido,
  ITEM_EM_HOMOLOGACAO,
  liquidoDaLinha,
  liquidoDoGrupo,
  meioDePagamento,
  montarNotaDeProduto,
  montarNotaDeServico,
  montarPagamentos,
  observacaoParaNota,
  NOME_EM_HOMOLOGACAO,
  normalizarCodigoLc116,
  partilhaDoIcms,
  pendenciasDaNotaDeProduto,
  pendenciasDaNotaDeServico,
  planejarNotas,
  temBloqueio,
  type DestinatarioDaNota,
  type EmissorDaNota,
  type ItemDaNota,
  type OpcoesDaNotaDeProduto,
  type PedidoDaNota,
  type PerfilFiscal,
} from "./montar.ts";
import { lerRegrasDeMercadoria, MODELOS_DE_MERCADORIA, MODELOS_DE_SERVICO } from "./regras.ts";

const CPF = "11144477735";
const CNPJ = "11222333000181";

const emissor: EmissorDaNota = {
  cnpj: CNPJ,
  razaoSocial: "Gráfica Teste Ltda",
  regime: "simplesNacional",
  uf: "PE",
  cidadeIbge: "2611606",
  inscricaoEstadual: "123456789",
  inscricaoMunicipal: "998877",
};

const perfilProducao: PerfilFiscal = {
  id: "perfil-producao",
  nome: "Produção própria",
  tipo: "mercadoria",
  padrao: true,
  ativo: true,
  regras: MODELOS_DE_MERCADORIA.find((m) => m.chave === "producao-simples")!.regras,
};

const perfilGrafico: PerfilFiscal = {
  id: "perfil-grafico",
  nome: "Serviço gráfico",
  tipo: "servico",
  padrao: true,
  ativo: true,
  regras: { ...MODELOS_DE_SERVICO[0].regras, aliquotaIss: 2.5 },
};

const perfilArte: PerfilFiscal = {
  id: "perfil-arte",
  nome: "Arte",
  tipo: "servico",
  padrao: false,
  ativo: true,
  regras: { ...MODELOS_DE_SERVICO[1].regras, aliquotaIss: 5, issRetido: "cliente_pj" },
};

const perfis = [perfilProducao, perfilGrafico, perfilArte];

function item(parcial: Partial<ItemDaNota>): ItemDaNota {
  return {
    id: parcial.id ?? "item-1",
    titulo: "Cartão de visita",
    produtoId: "prod-1",
    sku: null,
    codigoDeBarras: null,
    unitType: "unit",
    quantidade: 1,
    larguraM: null,
    alturaM: null,
    comprimentoM: null,
    valorTotal: 100,
    opcoes: [],
    fiscalTipo: null,
    ncm: "49119900",
    cest: null,
    origem: null,
    perfilId: null,
    ...parcial,
  };
}

function pedido(parcial: Partial<PedidoDaNota>): PedidoDaNota {
  return {
    id: "pedido-1",
    numero: 1234,
    total: 100,
    criadoEm: "2026-09-10T12:00:00Z",
    entregaEm: "2026-09-20",
    itens: [item({})],
    freteDoOrcamento: 0,
    descontoDoOrcamento: 0,
    temEntrega: false,
    pagamentos: [{ metodo: "pix", valor: 100 }],
    condicoes: null,
    ...parcial,
  };
}

const cliente: DestinatarioDaNota = {
  tipo: "pf",
  nome: "Maria da Silva",
  documento: CPF,
  email: "maria@example.com",
  telefone: "(81) 99999-0000",
  inscricaoEstadual: null,
  contribuinte: null,
  endereco: {
    cep: "50000-000",
    logradouro: "Rua do Sol",
    numero: "10",
    complemento: null,
    bairro: "Boa Vista",
    cidade: "Recife",
    uf: "PE",
    ibge: "2611606",
  },
};

describe("documentos e códigos", () => {
  it("valida CPF, CNPJ e GTIN", () => {
    assert.equal(documentoValido("111.444.777-35"), "cpf");
    assert.equal(documentoValido("11.222.333/0001-81"), "cnpj");
    assert.equal(documentoValido("11111111111"), null);
    assert.equal(gtinValido("7891000315507"), "7891000315507");
    assert.equal(gtinValido("7891000315508"), null);
    assert.equal(gtinValido("SEM GTIN"), null);
  });

  it("normaliza o código da LC 116", () => {
    assert.equal(normalizarCodigoLc116("13.05"), "13.05");
    assert.equal(normalizarCodigoLc116("01.06"), "1.06");
    assert.equal(normalizarCodigoLc116("1305"), "13.05");
    assert.equal(normalizarCodigoLc116(""), null);
  });

  it("traduz a forma de pagamento", () => {
    assert.equal(meioDePagamento("cash"), "money");
    assert.equal(meioDePagamento("credit_card"), "creditCard");
    assert.equal(meioDePagamento("debit_card"), "debitCard");
    assert.equal(meioDePagamento("transfer"), "bankTransfer");
    assert.equal(meioDePagamento("boleto"), "billetBanking");
    assert.equal(meioDePagamento("pix"), "pix");
    assert.equal(meioDePagamento("qualquer"), "commercialDuplicate");
  });
});

describe("planejarNotas", () => {
  it("lê a diferença para menos como desconto e fecha no centavo", () => {
    const plano = planejarNotas(
      pedido({
        total: 100,
        itens: [item({ id: "a", valorTotal: 33.33 }), item({ id: "b", valorTotal: 33.33 }), item({ id: "c", valorTotal: 43.34 })],
      }),
      { perfis, servicosDoCatalogo: [] }
    );
    assert.equal(plano.subtotal, 11000);
    assert.equal(plano.desconto, 1000);
    const soma = plano.mercadorias.reduce((acc, linha) => acc + liquidoDaLinha(linha), 0);
    assert.equal(soma, 10000);
    assert.ok(plano.mercadorias.every((linha) => linha.perfilId === "perfil-producao"));
  });

  it("usa frete e desconto do orçamento quando explicam a diferença", () => {
    const plano = planejarNotas(
      pedido({ total: 130, freteDoOrcamento: 40, descontoDoOrcamento: 10, temEntrega: true, itens: [item({ valorTotal: 100 })] }),
      { perfis, servicosDoCatalogo: [] }
    );
    assert.equal(plano.frete, 4000);
    assert.equal(plano.desconto, 1000);
    assert.equal(plano.mercadorias[0].frete, 4000);
    assert.equal(plano.mercadorias[0].desconto, 1000);
    assert.equal(liquidoDaLinha(plano.mercadorias[0]), 13000);
  });

  it("diferença para mais sem entrega vira outras despesas", () => {
    const plano = planejarNotas(pedido({ total: 105, itens: [item({ valorTotal: 100 })] }), { perfis, servicosDoCatalogo: [] });
    assert.equal(plano.outros, 500);
    assert.equal(plano.mercadorias[0].outros, 500);
  });

  it("produto por m² vai em M2 com a área total", () => {
    const plano = planejarNotas(
      pedido({
        total: 81.9,
        itens: [item({ unitType: "square_meter", quantidade: 2, larguraM: 1, alturaM: 0.7, valorTotal: 81.9, titulo: "Banner lona" })],
      }),
      { perfis, servicosDoCatalogo: [] }
    );
    const linha = plano.mercadorias[0];
    assert.equal(linha.unidade, "M2");
    assert.equal(linha.quantidade, 1.4);
    assert.match(linha.descricao, /Banner lona/);
    assert.match(linha.descricao, /1 m × 0,7 m/);
  });

  it("produto marcado como serviço vai inteiro para a NFS-e do perfil padrão", () => {
    const plano = planejarNotas(
      pedido({
        total: 150,
        itens: [item({ id: "a", valorTotal: 100 }), item({ id: "b", valorTotal: 50, fiscalTipo: "servico", titulo: "Criação de arte" })],
      }),
      { perfis, servicosDoCatalogo: [] }
    );
    assert.equal(plano.mercadorias.length, 1);
    assert.equal(plano.servicos.length, 1);
    assert.equal(plano.servicos[0].perfilId, "perfil-grafico");
    assert.equal(liquidoDoGrupo(plano.servicos[0]), 5000);
  });

  it("serviço opcional marcado como serviço sai da linha pelo valor cobrado", () => {
    const plano = planejarNotas(
      pedido({
        total: 130,
        itens: [
          item({
            quantidade: 3,
            valorTotal: 130,
            opcoes: [
              { optionId: "srv-instalacao", grupo: "Serviços", rotulo: "Instalação", preco: 10, price_unit: "piece" },
              { optionId: "srv-arte", grupo: "Serviços", rotulo: "Arte", preco: 20, price_unit: "order" },
              { optionId: "cor", grupo: "Cor", rotulo: "Azul", preco: 0 },
            ],
          }),
        ],
      }),
      {
        perfis,
        servicosDoCatalogo: [
          { id: "srv-instalacao", titulo: "Instalação", fiscalTipo: "servico", perfilId: null },
          { id: "srv-arte", titulo: "Arte", fiscalTipo: "servico", perfilId: "perfil-arte" },
        ],
      }
    );
    assert.equal(plano.mercadorias.length, 1);
    assert.equal(plano.mercadorias[0].valor, 8000);
    assert.match(plano.mercadorias[0].descricao, /Cor: Azul/);
    assert.doesNotMatch(plano.mercadorias[0].descricao, /Instalação/);
    const porPerfil = Object.fromEntries(plano.servicos.map((g) => [g.perfilId, liquidoDoGrupo(g)]));
    assert.deepEqual(porPerfil, { "perfil-grafico": 3000, "perfil-arte": 2000 });
  });

  it("pedido só de serviço leva o frete para dentro do serviço", () => {
    const plano = planejarNotas(
      pedido({ total: 120, temEntrega: true, itens: [item({ valorTotal: 100, fiscalTipo: "servico" })] }),
      { perfis, servicosDoCatalogo: [] }
    );
    assert.equal(plano.mercadorias.length, 0);
    assert.equal(liquidoDoGrupo(plano.servicos[0]), 12000);
  });

  it("marca como inconsistente pedido zerado", () => {
    const plano = planejarNotas(pedido({ total: 0 }), { perfis, servicosDoCatalogo: [] });
    assert.equal(plano.inconsistente, true);
  });
});

describe("montarPagamentos", () => {
  it("agrupa por meio, corta o excesso e põe cartão com grupo", () => {
    const r = montarPagamentos(
      pedido({ pagamentos: [{ metodo: "pix", valor: 60 }, { metodo: "credit_card", valor: 30 }, { metodo: "pix", valor: 20 }] }),
      10000,
      { modelo: "nfce" }
    );
    assert.deepEqual(r.payments, [
      { method: "pix", amount: 70 },
      { method: "creditCard", amount: 30, card: { isIntegratedPayment: false } },
    ]);
    assert.equal(r.emAberto, 0);
    assert.equal(r.duplicates.length, 0);
  });

  it("o que falta vira duplicata pelo cronograma das condições", () => {
    const condicoes = {
      v: 1,
      formas: ["boleto"],
      esquema: "entrada_e_saldo",
      a_vista: { quando: "aprovacao", dias: 30 },
      entrada: { tipo: "percentual", valor: 50 },
      saldo: { quando: "parcelado", dias: 30, parcelas: 2, intervalo_dias: 30 },
      parcelado: { parcelas: 3, intervalo_dias: 30, primeira: "aprovacao", dias: 30 },
      cartao: { parcelas: 1, sem_juros: true },
      observacoes: "",
    };
    const r = montarPagamentos(
      pedido({ total: 200, pagamentos: [{ metodo: "pix", valor: 100 }], condicoes, criadoEm: "2026-09-10T12:00:00-03:00" }),
      20000,
      { modelo: "nfe", agora: new Date("2026-09-16T15:00:00-03:00") }
    );
    assert.deepEqual(r.payments, [
      { method: "pix", amount: 100 },
      { method: "billetBanking", amount: 100 },
    ]);
    assert.deepEqual(
      r.duplicates.map((d) => [d.number, d.dueDate.slice(0, 10), d.amount]),
      [
        ["001", "2026-10-10", 50],
        ["002", "2026-11-09", 50],
      ]
    );
    assert.deepEqual(r.billing, { number: "1234", originalAmount: 100, discountAmount: 0, netAmount: 100 });
  });

  /* Rejeição 853 (NT 2025.001): parcela vencendo no dia da emissão é venda à
     vista, e à vista não leva cobrança. Era o que saía de todo pedido ainda
     não pago, e a SEFAZ recusava todos. */
  it("sem pagamento nenhum e sem condições, a nota sai à vista: sem fatura e sem duplicata", () => {
    const r = montarPagamentos(pedido({ pagamentos: [] }), 5000, { modelo: "nfe", agora: new Date("2026-09-16T15:00:00-03:00") });
    assert.deepEqual(r.payments, [{ method: "commercialDuplicate", amount: 50 }]);
    assert.deepEqual(r.duplicates, []);
    assert.equal(r.billing, null);
    assert.equal(r.emAberto, 5000);
  });

  it("pedido não pago com Pix nas condições sai como Pix à vista, sem duplicata (o caso do pedido #84)", () => {
    const condicoes = {
      v: 1,
      formas: ["pix"],
      esquema: "a_vista",
      a_vista: { quando: "aprovacao", dias: 0 },
      entrada: { tipo: "percentual", valor: 50 },
      saldo: { quando: "entrega", dias: 0, parcelas: 1, intervalo_dias: 30 },
      parcelado: { parcelas: 1, intervalo_dias: 30, primeira: "aprovacao", dias: 0 },
      cartao: { parcelas: 1, sem_juros: true },
      observacoes: "",
    };
    const r = montarPagamentos(
      pedido({ total: 2.5, pagamentos: [], condicoes, criadoEm: "2026-09-18T12:00:00-03:00" }),
      250,
      { modelo: "nfe", agora: new Date("2026-09-18T20:31:00-03:00") }
    );
    assert.deepEqual(r.payments, [{ method: "pix", amount: 2.5 }]);
    assert.deepEqual(r.duplicates, []);
    assert.equal(r.billing, null);
  });

  it("parcela única já vencida também é à vista", () => {
    const r = montarPagamentos(
      pedido({ pagamentos: [], formaEmAberto: "boleto", duplicatas: [{ vencimento: "2026-09-01", valor: 50 }] }),
      5000,
      { modelo: "nfe", agora: new Date("2026-09-16T15:00:00-03:00") }
    );
    assert.deepEqual(r.payments, [{ method: "billetBanking", amount: 50 }]);
    assert.deepEqual(r.duplicates, []);
    assert.equal(r.billing, null);
  });

  it("parcela para amanhã já é a prazo e leva a duplicata", () => {
    const r = montarPagamentos(
      pedido({ pagamentos: [], formaEmAberto: "boleto", duplicatas: [{ vencimento: "2026-09-17", valor: 50 }] }),
      5000,
      { modelo: "nfe", agora: new Date("2026-09-16T15:00:00-03:00") }
    );
    assert.deepEqual(
      r.duplicates.map((d) => [d.number, d.dueDate.slice(0, 10), d.amount]),
      [["001", "2026-09-17", 50]]
    );
    assert.deepEqual(r.billing, { number: "1234", originalAmount: 50, discountAmount: 0, netAmount: 50 });
  });

  it("entrada vencendo hoje com saldo no futuro continua a prazo, com as duas duplicatas", () => {
    const r = montarPagamentos(
      pedido({
        pagamentos: [],
        formaEmAberto: "boleto",
        duplicatas: [
          { vencimento: "2026-09-10", valor: 20 },
          { vencimento: "2026-10-16", valor: 30 },
        ],
      }),
      5000,
      { modelo: "nfe", agora: new Date("2026-09-16T15:00:00-03:00") }
    );
    assert.deepEqual(
      r.duplicates.map((d) => [d.number, d.dueDate.slice(0, 10), d.amount]),
      [
        ["001", "2026-09-16", 20],
        ["002", "2026-10-16", 30],
      ]
    );
    assert.equal(r.billing?.netAmount, 50);
  });

  it("cupom nunca leva duplicata", () => {
    const r = montarPagamentos(
      pedido({ pagamentos: [], formaEmAberto: "boleto", duplicatas: [{ vencimento: "2026-10-16", valor: 50 }] }),
      5000,
      { modelo: "nfce", agora: new Date("2026-09-16T15:00:00-03:00") }
    );
    assert.deepEqual(r.duplicates, []);
    assert.equal(r.billing, null);
  });
});

/* A observação do pedido vem do orçamento, escrita por gente: marcador,
   travessão, aspas curvas, emoji colado do WhatsApp. Nas informações
   complementares a SEFAZ só aceita uma linha em alfabeto latino básico, e um
   caractere fora disso recusa a nota inteira. */
describe("observacaoParaNota", () => {
  it("cada linha vira um trecho, e marcador vira hífen", () => {
    assert.equal(
      observacaoParaNota("Entregar na portaria\n• Falar com Ana\n\n– Arte aprovada em 12/09"),
      "Entregar na portaria | - Falar com Ana | - Arte aprovada em 12/09"
    );
  });

  it("acento passa; travessão, aspas curvas, emoji e símbolo de fora não", () => {
    assert.equal(observacaoParaNota("Não dobrar — “frágil” 🙂 €10"), 'Não dobrar - "frágil" 10');
    assert.equal(observacaoParaNota("Referência nº 3, 2ª via"), "Referência nº 3, 2ª via");
  });

  it("tudo o que sai cabe na regra de formato: uma linha, só latino básico, sem sobra nas pontas", () => {
    const saida = observacaoParaNota("  linha 1  \r\n\tlinha 2 ✅\n日本語\n");
    assert.equal(saida, "linha 1 | linha 2");
    assert.match(saida, /^[\u0021-\u00FF]([\u0020-\u00FF]*[\u0021-\u00FF])?$/);
  });

  it("vazio continua vazio", () => {
    assert.equal(observacaoParaNota(null), "");
    assert.equal(observacaoParaNota("  \n \n"), "");
    assert.equal(observacaoParaNota("🙂"), "");
  });
});

describe("montarNotaDeProduto", () => {
  it("a observação do pedido sai nas informações complementares, já limpa", () => {
    const p = pedido({ total: 90, itens: [item({ valorTotal: 90 })], pagamentos: [{ metodo: "pix", valor: 90 }] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor,
      destinatario: cliente,
      pedido: p,
      linhas: plano.mercadorias,
      perfis,
      opcoes: {
        modelo: "nfe",
        integrationId: "nota-obs",
        emitir: true,
        homologacao: false,
        consumidorFinal: true,
        presenca: "presence",
        enviarEmail: false,
        numeroDoPedido: 1234,
        observacao: "Entregar na portaria\n• Falar com Ana — tarde",
      },
    });
    const informacoes = String((nota.payload as Record<string, unknown>).additionalInformation);
    assert.ok(informacoes.startsWith("Pedido 1234 | "), informacoes);
    assert.match(informacoes, /Entregar na portaria \| - Falar com Ana - tarde$/);
  });

  it("NF-e do Simples: CFOP interno, CSOSN 102, PIS/COFINS 49 e total igual ao pedido", () => {
    const p = pedido({ total: 90, itens: [item({ valorTotal: 100, sku: "CV-01" })], pagamentos: [{ metodo: "pix", valor: 90 }] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor,
      destinatario: cliente,
      pedido: p,
      linhas: plano.mercadorias,
      perfis,
      opcoes: {
        modelo: "nfe",
        integrationId: "nota-1",
        emitir: true,
        homologacao: false,
        consumidorFinal: true,
        presenca: "presence",
        enviarEmail: true,
        numeroDoPedido: 1234,
      },
    });
    assert.equal(nota.valor, 9000);
    const payload = nota.payload as Record<string, any>;
    assert.equal(payload.destination, "internal");
    assert.equal(payload.items[0].cfop, 5101);
    assert.deepEqual(payload.items[0].taxes.icms, { origin: 0, csosn: 102 });
    assert.deepEqual(payload.items[0].taxes.pis, { cst: 49 });
    assert.equal(payload.items[0].discountAmount, 10);
    assert.equal(payload.items[0].code, "CV-01");
    assert.equal(payload.items[0].makeupTotal, true);
    assert.equal(payload.receiver.name, "Maria da Silva");
    assert.equal(payload.receiver.federalTaxNumber, CPF);
    assert.equal(payload.receiver.address.city.code, 2611606);
    assert.deepEqual(payload.payments, [{ method: "pix", amount: 90 }]);
    assert.equal(payload.transport.freightModality, "free");
    assert.match(payload.additionalInformation, /Pedido 1234/);
    assert.match(payload.additionalInformation, /Simples Nacional/);
  });

  it("cliente de outro estado usa o CFOP interestadual de consumidor", () => {
    const p = pedido({});
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor,
      destinatario: { ...cliente, endereco: { ...cliente.endereco!, uf: "SP", cidade: "São Paulo", ibge: "3550308" } },
      pedido: p,
      linhas: plano.mercadorias,
      perfis,
      opcoes: {
        modelo: "nfe",
        integrationId: "nota-2",
        emitir: true,
        homologacao: true,
        consumidorFinal: true,
        presenca: "internet",
        enviarEmail: false,
        numeroDoPedido: 1234,
      },
    });
    const payload = nota.payload as Record<string, any>;
    assert.equal(payload.destination, "interstate");
    assert.equal(payload.items[0].cfop, 6107);
    assert.equal(payload.receiver.name, NOME_EM_HOMOLOGACAO);
  });

  it("NFC-e em homologação troca a descrição do primeiro item e só leva o CPF", () => {
    const p = pedido({ itens: [item({ id: "a", valorTotal: 50 }), item({ id: "b", valorTotal: 50 })] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor,
      destinatario: cliente,
      pedido: p,
      linhas: plano.mercadorias,
      perfis,
      opcoes: {
        modelo: "nfce",
        integrationId: "nota-3",
        emitir: true,
        homologacao: true,
        consumidorFinal: true,
        presenca: "presence",
        enviarEmail: false,
        numeroDoPedido: null,
      },
    });
    const payload = nota.payload as Record<string, any>;
    assert.equal(payload.items[0].description, ITEM_EM_HOMOLOGACAO);
    assert.notEqual(payload.items[1].description, ITEM_EM_HOMOLOGACAO);
    assert.deepEqual(Object.keys(payload.receiver).sort(), ["federalTaxNumber", "name"]);
    assert.equal(payload.presenceType, "presence");
    assert.equal(payload.duplicates, undefined);
  });

  it("regime normal calcula base e valor do ICMS, PIS e COFINS", () => {
    const perfilNormal: PerfilFiscal = {
      id: "normal",
      nome: "Normal",
      tipo: "mercadoria",
      padrao: true,
      ativo: true,
      regras: {
        ...MODELOS_DE_MERCADORIA.find((m) => m.chave === "producao-normal")!.regras,
        aliquotaIcms: 18,
        pisAliquota: 0.65,
        cofinsAliquota: 3,
      },
    };
    const p = pedido({ total: 90, itens: [item({ valorTotal: 100 })], pagamentos: [{ metodo: "cash", valor: 90 }] });
    const plano = planejarNotas(p, { perfis: [perfilNormal], servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor: { ...emissor, regime: "regimeNormal" },
      destinatario: cliente,
      pedido: p,
      linhas: plano.mercadorias,
      perfis: [perfilNormal],
      opcoes: {
        modelo: "nfe",
        integrationId: "nota-4",
        emitir: false,
        homologacao: false,
        consumidorFinal: true,
        presenca: "presence",
        enviarEmail: false,
        numeroDoPedido: 1,
      },
    });
    const taxes = (nota.payload as Record<string, any>).items[0].taxes;
    assert.deepEqual(taxes.icms, { origin: 0, cst: 0, baseTaxModality: 3, baseTax: 90, rate: 18, amount: 16.2 });
    assert.deepEqual(taxes.pis, { cst: 1, baseTax: 90, rate: 0.65, amount: 0.59 });
    assert.deepEqual(taxes.cofins, { cst: 1, baseTax: 90, rate: 3, amount: 2.7 });
    /* Reforma tributária: no regime normal o grupo vai completo (só CST e classificação
       a SEFAZ conta como "não informado"); no Simples ele nem aparece. */
    assert.deepEqual(taxes.ibsCbs, {
      cst: 0,
      classification: 1,
      baseTax: 90,
      ibsStateRate: 0.1,
      ibsStateAmount: 0.09,
      ibsCityRate: 0,
      ibsCityAmount: 0,
      ibsAmount: 0.09,
      cbsRate: 0.9,
      cbsAmount: 0.81,
    });
    assert.equal((nota.payload as Record<string, any>).issue, false);
  });

  describe("cliente de outro estado no regime normal (o caso da Focus: PA → SP)", () => {
    const perfilNormal: PerfilFiscal = {
      id: "normal",
      nome: "Normal",
      tipo: "mercadoria",
      padrao: true,
      ativo: true,
      regras: { ...MODELOS_DE_MERCADORIA.find((m) => m.chave === "producao-normal")!.regras, aliquotaIcms: 19 },
    };
    const doPara: EmissorDaNota = { ...emissor, regime: "regimeNormal", uf: "PA", cidadeIbge: "1501402" };
    const empresaDeSp: DestinatarioDaNota = {
      ...cliente,
      tipo: "pj",
      documento: CNPJ,
      contribuinte: "contribuinte",
      inscricaoEstadual: "664070273113",
      endereco: { ...cliente.endereco!, uf: "SP", cidade: "Sertãozinho", ibge: "3551702" },
    };
    const montar = (presenca: OpcoesDaNotaDeProduto["presenca"], temEntrega = false) => {
      const p = pedido({ total: 100, temEntrega, itens: [item({ valorTotal: 100 })], pagamentos: [{ metodo: "pix", valor: 100 }] });
      const plano = planejarNotas(p, { perfis: [perfilNormal], servicosDoCatalogo: [] });
      return montarNotaDeProduto({
        emissor: doPara,
        destinatario: empresaDeSp,
        pedido: p,
        linhas: plano.mercadorias,
        perfis: [perfilNormal],
        opcoes: {
          modelo: "nfe",
          integrationId: "nota-focus",
          emitir: false,
          homologacao: false,
          consumidorFinal: true,
          presenca,
          enviarEmail: false,
          numeroDoPedido: 1,
        },
      }).payload as Record<string, any>;
    };

    it("enviada para SP: CFOP 6 e ICMS de 12%, não a alíquota interna do perfil (rejeição 693)", () => {
      const payload = montar("othersNonPresenceOperation");
      assert.equal(payload.destination, "interstate");
      assert.equal(String(payload.items[0].cfop)[0], "6");
      assert.deepEqual(payload.items[0].taxes.icms, { origin: 0, cst: 0, baseTaxModality: 3, baseTax: 100, rate: 12, amount: 12 });
      assert.equal(payload.items[0].taxes.icmsDestination, undefined, "contribuinte não leva partilha na nota");
    });

    it("retirada no balcão: venda dentro do estado, CFOP 5 e a alíquota do perfil", () => {
      const payload = montar("presence");
      assert.equal(payload.destination, "internal");
      assert.equal(payload.presenceType, "presence");
      assert.equal(String(payload.items[0].cfop)[0], "5");
      assert.equal(payload.items[0].taxes.icms.rate, 19);
      assert.equal(payload.receiver.address.city.state, "SP", "o endereço do cliente continua o dele");
    });

    it("presencial mas com entrega continua interestadual", () => {
      const payload = montar("presence", true);
      assert.equal(payload.destination, "interstate");
      assert.equal(payload.items[0].taxes.icms.rate, 12);
    });

    it("a janela avisa que a venda presencial de cliente de fora sai como venda dentro do estado", () => {
      const p = pedido({});
      const plano = planejarNotas(p, { perfis: [perfilNormal], servicosDoCatalogo: [] });
      const base = {
        modelo: "nfe" as const,
        emissor: doPara,
        destinatario: empresaDeSp,
        linhas: plano.mercadorias,
        perfis: [perfilNormal],
        consumidorFinal: true,
        total: 10000,
      };
      const balcao = pendenciasDaNotaDeProduto({ ...base, presenca: "presence", temEntrega: false });
      assert.ok(balcao.some((x) => x.codigo === "venda.retirada_no_balcao" && x.nivel === "aviso"));
      const enviada = pendenciasDaNotaDeProduto({ ...base, presenca: "internet", temEntrega: false });
      assert.equal(enviada.some((x) => x.codigo === "venda.retirada_no_balcao"), false);
    });
  });

  it("no Simples Nacional a nota sai sem o grupo de IBS/CBS", () => {
    const p = pedido({ total: 90, itens: [item({ valorTotal: 100 })], pagamentos: [{ metodo: "cash", valor: 90 }] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nota = montarNotaDeProduto({
      emissor,
      destinatario: cliente,
      pedido: p,
      linhas: plano.mercadorias,
      perfis,
      opcoes: {
        modelo: "nfe",
        integrationId: "nota-simples-ibs",
        emitir: false,
        homologacao: false,
        consumidorFinal: true,
        presenca: "presence",
        enviarEmail: false,
        numeroDoPedido: 1,
      },
    });
    assert.equal((nota.payload as Record<string, any>).items[0].taxes.ibsCbs, undefined);
  });
});

describe("montarNotaDeServico", () => {
  it("ISS sobre o valor com desconto, retido quando o tomador é empresa", () => {
    const p = pedido({ total: 180, itens: [item({ valorTotal: 200, fiscalTipo: "servico", perfilId: "perfil-arte" })] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const grupo = plano.servicos[0];
    const tomadorPj: DestinatarioDaNota = { ...cliente, tipo: "pj", documento: CNPJ, nome: "Empresa X" };
    const nota = montarNotaDeServico({
      emissor,
      destinatario: tomadorPj,
      grupo,
      perfil: perfilArte,
      opcoes: { integrationId: "nfse-1", emitir: true, enviarEmail: true, numeroDoPedido: 1234 },
    });
    const payload = nota.payload as Record<string, any>;
    assert.equal(payload.federalServiceCode, "23.01");
    assert.deepEqual(payload.total, {
      invoiceAmount: 200,
      issBaseTax: 180,
      issRate: 5,
      issAmount: 9,
      issWithheld: true,
      netAmount: 171,
      discountUnconditionedAmount: 20,
    });
    assert.equal(nota.valor, 18000);
    assert.match(payload.description, /Pedido 1234/);
    assert.equal(payload.receiver.federalTaxNumber, CNPJ);
  });

  it("o endereço do cliente vai sem país na nota de serviço, e com BRA na nota de produto", () => {
    /* Como na nota de Campinas que a Spedy dá de exemplo. Ela mesma preenche
       "BRA" quando o país falta. */
    const servico = destinatarioParaNota(cliente, { modelo: "nfse", homologacao: false }) as Record<string, any>;
    assert.equal(servico.address.country, undefined);
    assert.equal(servico.address.postalCode, "50000000");
    assert.equal(servico.address.city.code, 2611606);
    assert.equal(servico.federalTaxNumber, CPF);
    const produto = destinatarioParaNota(cliente, { modelo: "nfe", homologacao: false }) as Record<string, any>;
    assert.equal(produto.address.country, "BRA");
  });

  it("onde o ISS é devido vai sempre: a cidade da empresa por padrão, a escolha do contador quando houver", () => {
    /* Sem o campo, a prefeitura de Campinas (DSF) recusou com "tipoIncidencia
       is null", mesmo com a Spedy dizendo que assume a cidade da empresa. */
    const p = pedido({ total: 200, itens: [item({ valorTotal: 200, fiscalTipo: "servico", perfilId: "perfil-arte" })] });
    const grupo = planejarNotas(p, { perfis, servicosDoCatalogo: [] }).servicos[0];
    const montar = (localIncidencia: string | null) =>
      montarNotaDeServico({
        emissor,
        destinatario: cliente,
        grupo,
        perfil: { ...perfilArte, regras: { ...(perfilArte.regras as Record<string, unknown>), localIncidencia } },
        opcoes: { integrationId: "nfse-local", emitir: true, enviarEmail: false, numeroDoPedido: null },
      }).payload as Record<string, unknown>;
    assert.equal(montar(null).taxLocation, "companyMunicipality");
    assert.equal(montar("customerMunicipality").taxLocation, "customerMunicipality");
    assert.equal(montar("serviceProvisionMunicipality").taxLocation, "serviceProvisionMunicipality");
    /* Valor fora da lista não passa: vale a cidade da empresa. */
    assert.equal(montar("qualquerCoisa").taxLocation, "companyMunicipality");
  });

  describe("códigos do serviço", () => {
    const emCampinas: EmissorDaNota = { ...emissor, uf: "SP", cidadeIbge: CAMPINAS };
    const perfilDaGrafica: PerfilFiscal = {
      ...perfilGrafico,
      regras: {
        ...(perfilGrafico.regras as Record<string, unknown>),
        codigoLc116: "13.05",
        codigoMunicipal: "181309900",
        cnae: "1813-0/99",
      },
    };
    const grupoDoServico = () =>
      planejarNotas(pedido({ total: 1, itens: [item({ valorTotal: 1, fiscalTipo: "servico", perfilId: "perfil-grafico" })] }), {
        perfis: [perfilDaGrafica],
        servicosDoCatalogo: [],
      }).servicos[0];

    it("em Campinas o código da prefeitura vai no campo do CNAE, o item sem ponto e sem o código municipal", () => {
      /* O arranjo com que a Spedy autorizou a primeira nota da MK5 (NFS-e 6136). */
      const payload = montarNotaDeServico({
        emissor: emCampinas,
        destinatario: cliente,
        grupo: grupoDoServico(),
        perfil: perfilDaGrafica,
        opcoes: { integrationId: "nfse-campinas", emitir: true, enviarEmail: false, numeroDoPedido: null },
      }).payload as Record<string, unknown>;
      assert.equal(payload.cnaeCode, "181309900");
      assert.equal(payload.federalServiceCode, "1305");
      assert.equal("cityServiceCode" in payload, false);
    });

    it("em Campinas o item de um algarismo no grupo vai com três números, como na tabela da prefeitura", () => {
      assert.deepEqual(codigosDoServico({ codigoLc116: "04.22", codigoMunicipal: "8630-5/03-01", cnae: "8630503" }, CAMPINAS), {
        federalServiceCode: "422",
        cnaeCode: "863050301",
      });
    });

    it("fora de Campinas os três códigos seguem como a empresa preencheu", () => {
      const payload = montarNotaDeServico({
        emissor,
        destinatario: cliente,
        grupo: grupoDoServico(),
        perfil: perfilDaGrafica,
        opcoes: { integrationId: "nfse-recife", emitir: true, enviarEmail: false, numeroDoPedido: null },
      }).payload as Record<string, unknown>;
      assert.equal(payload.federalServiceCode, "13.05");
      assert.equal(payload.cityServiceCode, "181309900");
      assert.equal(payload.cnaeCode, "1813099");
      assert.deepEqual(codigosDoServico({ codigoLc116: "1.01", codigoMunicipal: "1.01/102301", cnae: null }, "3548708"), {
        federalServiceCode: "1.01",
        cityServiceCode: "1.01/102301",
      });
    });

    it("no portal nacional, a nota de quem é do regime normal vai sem a alíquota do ISS", () => {
      /* O arranjo com que a Spedy autorizou o teste da Focus (Parauapebas, 02/10/2026). */
      const perfilDaFocus: PerfilFiscal = {
        ...perfilGrafico,
        regras: { ...(perfilGrafico.regras as Record<string, unknown>), codigoLc116: "13.05", codigoMunicipal: null, nbs: "121012100", ibsCbsIndicadorOperacao: "030101" },
      };
      const montar = (regime: EmissorDaNota["regime"], padraoNacional: boolean) =>
        montarNotaDeServico({
          emissor: { ...emissor, regime, uf: "PA", cidadeIbge: "1505536" },
          destinatario: cliente,
          grupo: grupoDoServico(),
          perfil: perfilDaFocus,
          opcoes: { integrationId: "nfse-nacional", emitir: true, enviarEmail: false, numeroDoPedido: null, padraoNacional },
        }).payload as Record<string, any>;

      const normal = montar("regimeNormal", true);
      assert.equal("issRate" in normal.total, false);
      assert.equal(normal.nbsCode, "121012100");
      assert.equal("cityServiceCode" in normal, false);
      assert.deepEqual(normal.ibsCbs, { cst: 0, classification: 1, operationIndicatorCode: "030101" });

      /* Quem é do Simples informa a alíquota da faixa; cidade com sistema próprio também informa. */
      assert.equal("issRate" in montar("simplesNacional", true).total, true);
      assert.equal("issRate" in montar("regimeNormal", false).total, true);
    });

    it("em Campinas, código da prefeitura sem os 9 números segura a nota antes de ir", () => {
      const pendencias = (codigoMunicipal: string | null, exige = { codigoLc116: true, codigoMunicipal: true, cnae: true }) =>
        pendenciasDaNotaDeServico({
          emissor: emCampinas,
          destinatario: cliente,
          grupo: grupoDoServico(),
          perfis: [{ ...perfilDaGrafica, regras: { ...(perfilDaGrafica.regras as Record<string, unknown>), codigoMunicipal } }],
          exige,
        });
      const doCodigo = (lista: ReturnType<typeof pendencias>) => lista.filter((p) => p.codigo.startsWith("perfil.codigo_de_campinas"));

      assert.equal(doCodigo(pendencias("181309900")).length, 0);
      const itemDaLista = doCodigo(pendencias("13.05"));
      assert.equal(itemDaLista.length, 1);
      assert.equal(itemDaLista[0].nivel, "bloqueio");
      assert.match(itemDaLista[0].detalhe ?? "", /9 números/);
      assert.match(itemDaLista[0].detalhe ?? "", /"13\.05"/);
      /* Vazio, quem avisa é a falta do código; sem a lista da cidade, avisa o de Campinas. */
      assert.equal(doCodigo(pendencias(null)).length, 0);
      assert.equal(doCodigo(pendencias(null, { codigoLc116: true, codigoMunicipal: false, cnae: false })).length, 1);
      /* Em outra cidade o formato é outro e não há essa trava. */
      const emRecife = pendenciasDaNotaDeServico({
        emissor,
        destinatario: cliente,
        grupo: grupoDoServico(),
        perfis: [{ ...perfilDaGrafica, regras: { ...(perfilDaGrafica.regras as Record<string, unknown>), codigoMunicipal: "13.05" } }],
        exige: { codigoLc116: true },
      });
      assert.equal(doCodigo(emRecife).length, 0);
    });
  });
});

describe("pendências", () => {
  it("NF-e sem NCM, sem documento e sem endereço bloqueia com o motivo certo", () => {
    const p = pedido({ itens: [item({ ncm: null })] });
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const pendencias = pendenciasDaNotaDeProduto({
      modelo: "nfe",
      emissor,
      destinatario: { ...cliente, documento: null, endereco: { ...cliente.endereco!, bairro: "" } },
      linhas: plano.mercadorias,
      perfis,
      consumidorFinal: true,
      total: 10000,
    });
    const codigos = pendencias.map((p) => p.codigo);
    assert.ok(codigos.includes("cliente.documento"));
    assert.ok(codigos.includes("cliente.endereco"));
    assert.ok(codigos.some((c) => c.startsWith("produto.ncm")));
    assert.equal(temBloqueio(pendencias), true);
  });

  it("NF-e completa não tem bloqueio", () => {
    const p = pedido({});
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const pendencias = pendenciasDaNotaDeProduto({
      modelo: "nfe",
      emissor,
      destinatario: cliente,
      linhas: plano.mercadorias,
      perfis,
      consumidorFinal: true,
      total: 10000,
    });
    assert.equal(temBloqueio(pendencias), false);
  });

  it("NFC-e com valor em aberto bloqueia, e a partilha do ICMS pede a alíquota do destino", () => {
    const p = pedido({});
    const plano = planejarNotas(p, { perfis, servicosDoCatalogo: [] });
    const nfce = pendenciasDaNotaDeProduto({
      modelo: "nfce",
      emissor,
      destinatario: null,
      linhas: plano.mercadorias,
      perfis,
      consumidorFinal: true,
      total: 10000,
      emAberto: 500,
    });
    assert.ok(nfce.some((x) => x.codigo === "pagamento.nfce_em_aberto"));

    const difal = pendenciasDaNotaDeProduto({
      modelo: "nfe",
      emissor: { ...emissor, regime: "regimeNormal" },
      destinatario: { ...cliente, endereco: { ...cliente.endereco!, uf: "SP" } },
      linhas: plano.mercadorias,
      perfis,
      consumidorFinal: true,
      total: 10000,
    });
    assert.ok(
      difal.some((x) => x.codigo.startsWith("perfil.difal") && /alíquota de ICMS de SP/i.test(x.titulo)),
      "sem a alíquota interna do destino, a nota para e diz o que falta"
    );

    /* Com a alíquota informada pelo contador, a nota sai: a partilha é calculada. */
    const comAliquota = pendenciasDaNotaDeProduto({
      modelo: "nfe",
      emissor: { ...emissor, regime: "regimeNormal" },
      destinatario: { ...cliente, endereco: { ...cliente.endereco!, uf: "SP" } },
      linhas: plano.mercadorias,
      perfis: [
        {
          ...perfilProducao,
          regras: { ...lerRegrasDeMercadoria(perfilProducao.regras), cstIcms: 0, aliquotaIcms: 20.5, aliquotaInternaPorUf: { SP: 18 } },
        },
      ],
      consumidorFinal: true,
      total: 10000,
    });
    assert.equal(comAliquota.some((x) => x.codigo.startsWith("perfil.difal")), false);
  });

  it("partilha do ICMS: base por dentro do destino e nada para o remetente", () => {
    /* PE → SP, mercadoria nacional: 12% entre estados, 18% interna em SP. */
    const partilha = partilhaDoIcms({ base: 10000, ufOrigem: "PE", ufDestino: "SP", origemDaMercadoria: 0, aliquotaInterna: 18 });
    assert.ok(partilha);
    assert.equal(partilha.entreEstados, 12);
    assert.equal(partilha.baseNoDestino, 10732, "base calculada por dentro com a alíquota do destino");
    assert.equal(partilha.icmsDoDestino, 732);

    /* Do Sudeste para o Nordeste a alíquota entre estados é 7%. */
    assert.equal(partilhaDoIcms({ base: 10000, ufOrigem: "SP", ufDestino: "PE", origemDaMercadoria: 0, aliquotaInterna: 20.5 })?.entreEstados, 7);
    /* Mercadoria importada: 4%, venha de onde vier. */
    assert.equal(partilhaDoIcms({ base: 10000, ufOrigem: "PE", ufDestino: "SP", origemDaMercadoria: 1, aliquotaInterna: 18 })?.entreEstados, 4);
    /* Sem a alíquota do destino não há cálculo: quem decide é o contador. */
    assert.equal(partilhaDoIcms({ base: 10000, ufOrigem: "PE", ufDestino: "SP", origemDaMercadoria: 0, aliquotaInterna: 0 }), null);
  });

  it("perfil incompleto e serviço sem alíquota aparecem", () => {
    const incompleto: PerfilFiscal = { ...perfilProducao, regras: { natureza: "Venda" } };
    const p = pedido({});
    const plano = planejarNotas(p, { perfis: [incompleto], servicosDoCatalogo: [] });
    const pendencias = pendenciasDaNotaDeProduto({
      modelo: "nfe",
      emissor,
      destinatario: cliente,
      linhas: plano.mercadorias,
      perfis: [incompleto],
      consumidorFinal: true,
      total: 10000,
    });
    assert.ok(pendencias.some((x) => x.codigo === `perfil.incompleto:${incompleto.id}` && /CFOP/.test(x.detalhe ?? "")));

    const semAliquota: PerfilFiscal = { ...perfilGrafico, regras: MODELOS_DE_SERVICO[0].regras };
    const planoServico = planejarNotas(pedido({ itens: [item({ fiscalTipo: "servico" })] }), {
      perfis: [semAliquota],
      servicosDoCatalogo: [],
    });
    const deServico = pendenciasDaNotaDeServico({
      emissor,
      destinatario: cliente,
      grupo: planoServico.servicos[0],
      perfis: [semAliquota],
      exige: { codigoLc116: true },
    });
    assert.ok(deServico.some((x) => /alíquota do ISS/.test(x.detalhe ?? "")));
  });
});
