/* As condições comerciais do orçamento: como e quando o cliente paga.

   Moram em `quotes.payment_terms` (jsonb) e, quando o orçamento vira pedido,
   a função do banco copia o mesmo JSON para `orders.payment_terms` — o pedido
   guarda o que foi combinado, mesmo que o orçamento mude depois. O perfil da
   empresa guarda um padrão em `profiles.quote_default_payment_terms`, que é o
   ponto de partida de todo orçamento novo.

   Este arquivo não desenha nada: só sabe ler o JSON (com defesa contra o que
   vier de fora), montar o cronograma de parcelas a partir do total e das
   datas, e escrever as condições em português para a proposta.

   Os códigos das formas de pagamento são os mesmos de `payments.payment_method`
   ("pix", "cash", "credit_card"...), para a parcela combinada virar um
   pagamento registrado sem tradução no meio do caminho.

   AVISO: este arquivo é uma cópia de
   `apps/admin/src/lib/condicoes-comerciais.ts`, porque os apps não
   compartilham pacote. O original é o do admin: mudou lá, muda aqui. */

export type FormaDePagamento = "pix" | "cash" | "credit_card" | "debit_card" | "boleto" | "transfer" | "link";

export type Esquema = "a_vista" | "entrada_e_saldo" | "parcelado";

/* Quando um pagamento acontece. "faturado" é o prazo do B2B: N dias depois
   da entrega. */
export type Momento = "aprovacao" | "entrega" | "retirada" | "faturado";

export type CondicoesComerciais = {
  v: 1;
  formas: FormaDePagamento[];
  esquema: Esquema;
  a_vista: { quando: Momento; dias: number };
  entrada: { tipo: "percentual" | "valor"; valor: number };
  saldo: { quando: Momento | "parcelado"; dias: number; parcelas: number; intervalo_dias: number };
  parcelado: { parcelas: number; intervalo_dias: number; primeira: "aprovacao" | "entrega" | "dias"; dias: number };
  /* Só informativo, para quem aceita cartão de crédito: "em até 3x sem juros". */
  cartao: { parcelas: number; sem_juros: boolean };
  observacoes: string;
};

export const FORMAS_DE_PAGAMENTO: ReadonlyArray<{ codigo: FormaDePagamento; nome: string; curto: string }> = [
  { codigo: "pix", nome: "Pix", curto: "Pix" },
  { codigo: "cash", nome: "Dinheiro", curto: "dinheiro" },
  { codigo: "credit_card", nome: "Cartão de crédito", curto: "cartão de crédito" },
  { codigo: "debit_card", nome: "Cartão de débito", curto: "cartão de débito" },
  { codigo: "boleto", nome: "Boleto bancário", curto: "boleto" },
  { codigo: "transfer", nome: "Transferência bancária", curto: "transferência" },
  { codigo: "link", nome: "Link de pagamento", curto: "link de pagamento" },
];

const CODIGOS_DAS_FORMAS = new Set<string>(FORMAS_DE_PAGAMENTO.map((forma) => forma.codigo));

export const LIMITES = {
  parcelas: { minimo: 2, maximo: 24 },
  parcelasNoCartao: { minimo: 1, maximo: 12 },
  dias: { minimo: 1, maximo: 365 },
  intervaloDias: { minimo: 1, maximo: 120 },
  entradaPercentual: { minimo: 1, maximo: 99 },
} as const;

export function padraoDasCondicoes(): CondicoesComerciais {
  return {
    v: 1,
    formas: ["pix"],
    esquema: "a_vista",
    a_vista: { quando: "aprovacao", dias: 30 },
    entrada: { tipo: "percentual", valor: 50 },
    saldo: { quando: "entrega", dias: 30, parcelas: 2, intervalo_dias: 30 },
    parcelado: { parcelas: 3, intervalo_dias: 30, primeira: "aprovacao", dias: 30 },
    cartao: { parcelas: 1, sem_juros: true },
    observacoes: "",
  };
}

export function nomeDaForma(codigo: string): string {
  return FORMAS_DE_PAGAMENTO.find((forma) => forma.codigo === codigo)?.nome ?? codigo;
}

/* ------------------------------------------------------------------ */
/* Leitura defensiva do JSON                                           */

function ehObjeto(valor: unknown): valor is Record<string, unknown> {
  return Boolean(valor) && typeof valor === "object" && !Array.isArray(valor);
}

function inteiroEntre(valor: unknown, minimo: number, maximo: number, padrao: number) {
  const numero = typeof valor === "number" ? valor : typeof valor === "string" ? Number(valor) : NaN;
  if (!Number.isFinite(numero)) return padrao;
  return Math.min(maximo, Math.max(minimo, Math.round(numero)));
}

function numeroNaoNegativo(valor: unknown, padrao: number) {
  const numero = typeof valor === "number" ? valor : typeof valor === "string" ? Number(valor) : NaN;
  if (!Number.isFinite(numero) || numero < 0) return padrao;
  return Math.round(numero * 100) / 100;
}

function umDe<T extends string>(valor: unknown, opcoes: readonly T[], padrao: T): T {
  return typeof valor === "string" && (opcoes as readonly string[]).includes(valor) ? (valor as T) : padrao;
}

const MOMENTOS: readonly Momento[] = ["aprovacao", "entrega", "retirada", "faturado"];

/* Aceita o que estiver guardado e completa o que faltar com o padrão. Devolve
   null só quando não há condições nenhuma (orçamento antigo, coluna nula). */
export function normalizarCondicoes(bruto: unknown): CondicoesComerciais | null {
  if (!ehObjeto(bruto)) return null;
  const padrao = padraoDasCondicoes();

  const formas = Array.isArray(bruto.formas)
    ? (bruto.formas.filter((forma): forma is FormaDePagamento => typeof forma === "string" && CODIGOS_DAS_FORMAS.has(forma)) as FormaDePagamento[])
    : [];
  const formasSemRepeticao = Array.from(new Set(formas));

  const aVista = ehObjeto(bruto.a_vista) ? bruto.a_vista : {};
  const entrada = ehObjeto(bruto.entrada) ? bruto.entrada : {};
  const saldo = ehObjeto(bruto.saldo) ? bruto.saldo : {};
  const parcelado = ehObjeto(bruto.parcelado) ? bruto.parcelado : {};
  const cartao = ehObjeto(bruto.cartao) ? bruto.cartao : {};

  const tipoDaEntrada = umDe(entrada.tipo, ["percentual", "valor"] as const, padrao.entrada.tipo);

  return {
    v: 1,
    formas: formasSemRepeticao,
    esquema: umDe(bruto.esquema, ["a_vista", "entrada_e_saldo", "parcelado"] as const, padrao.esquema),
    a_vista: {
      quando: umDe(aVista.quando, MOMENTOS, padrao.a_vista.quando),
      dias: inteiroEntre(aVista.dias, LIMITES.dias.minimo, LIMITES.dias.maximo, padrao.a_vista.dias),
    },
    entrada: {
      tipo: tipoDaEntrada,
      valor:
        tipoDaEntrada === "percentual"
          ? inteiroEntre(entrada.valor, LIMITES.entradaPercentual.minimo, LIMITES.entradaPercentual.maximo, padrao.entrada.valor)
          : numeroNaoNegativo(entrada.valor, 0),
    },
    saldo: {
      quando: umDe(saldo.quando, [...MOMENTOS, "parcelado"] as const, padrao.saldo.quando),
      dias: inteiroEntre(saldo.dias, LIMITES.dias.minimo, LIMITES.dias.maximo, padrao.saldo.dias),
      parcelas: inteiroEntre(saldo.parcelas, LIMITES.parcelas.minimo, LIMITES.parcelas.maximo, padrao.saldo.parcelas),
      intervalo_dias: inteiroEntre(saldo.intervalo_dias, LIMITES.intervaloDias.minimo, LIMITES.intervaloDias.maximo, padrao.saldo.intervalo_dias),
    },
    parcelado: {
      parcelas: inteiroEntre(parcelado.parcelas, LIMITES.parcelas.minimo, LIMITES.parcelas.maximo, padrao.parcelado.parcelas),
      intervalo_dias: inteiroEntre(parcelado.intervalo_dias, LIMITES.intervaloDias.minimo, LIMITES.intervaloDias.maximo, padrao.parcelado.intervalo_dias),
      primeira: umDe(parcelado.primeira, ["aprovacao", "entrega", "dias"] as const, padrao.parcelado.primeira),
      dias: inteiroEntre(parcelado.dias, LIMITES.dias.minimo, LIMITES.dias.maximo, padrao.parcelado.dias),
    },
    cartao: {
      parcelas: inteiroEntre(cartao.parcelas, LIMITES.parcelasNoCartao.minimo, LIMITES.parcelasNoCartao.maximo, padrao.cartao.parcelas),
      sem_juros: cartao.sem_juros !== false,
    },
    observacoes: typeof bruto.observacoes === "string" ? bruto.observacoes.trim().slice(0, 1000) : "",
  };
}

/* O que barra o salvar. Devolve a mensagem ou null. */
export function erroDasCondicoes(condicoes: CondicoesComerciais, total: number): string | null {
  if (condicoes.formas.length === 0) return "Escolha ao menos uma forma de pagamento.";
  if (condicoes.esquema === "entrada_e_saldo") {
    if (condicoes.entrada.tipo === "valor") {
      if (condicoes.entrada.valor <= 0) return "Informe o valor da entrada.";
      if (total > 0 && condicoes.entrada.valor >= total) return "A entrada precisa ser menor que o total do orçamento.";
    }
  }
  return null;
}

/* ------------------------------------------------------------------ */
/* Datas e dinheiro                                                    */

function diaLocal(valor: string | null | undefined): string | null {
  if (!valor) return null;
  const somenteData = valor.slice(0, 10);
  return /^\d{4}-\d{2}-\d{2}$/.test(somenteData) ? somenteData : null;
}

function somarDias(dia: string, dias: number): string {
  const data = new Date(`${dia}T12:00:00Z`);
  data.setUTCDate(data.getUTCDate() + dias);
  return data.toISOString().slice(0, 10);
}

export function formatarDia(dia: string | null | undefined): string {
  const somenteData = diaLocal(dia);
  if (!somenteData) return "";
  const [ano, mes, diaDoMes] = somenteData.split("-");
  return `${diaDoMes}/${mes}/${ano}`;
}

function centavos(valor: number) {
  return Math.round((Number.isFinite(valor) ? valor : 0) * 100) / 100;
}

/* Divide em parcelas iguais e joga a diferença dos centavos na última. */
export function dividirEmParcelas(total: number, quantidade: number): number[] {
  const n = Math.max(1, Math.floor(quantidade));
  const totalEmCentavos = Math.round(centavos(total) * 100);
  const base = Math.floor(totalEmCentavos / n);
  const parcelas = Array.from({ length: n }, () => base);
  parcelas[n - 1] = totalEmCentavos - base * (n - 1);
  return parcelas.map((valor) => valor / 100);
}

export function valorDaEntrada(condicoes: CondicoesComerciais, total: number): number {
  if (condicoes.entrada.tipo === "percentual") return centavos((centavos(total) * condicoes.entrada.valor) / 100);
  return centavos(Math.min(condicoes.entrada.valor, Math.max(0, total)));
}

/* ------------------------------------------------------------------ */
/* O cronograma                                                        */

export type ParcelaDoCronograma = {
  numero: number;
  rotulo: string;
  valor: number;
  /* "na aprovação", "na entrega", "30 dias após a entrega"... */
  quando: string;
  /* A data, quando dá para saber. Na proposta a aprovação ainda não aconteceu,
     então as parcelas presas a ela ficam sem data até o pedido nascer. */
  vencimento: string | null;
  /* De que data esta parcela depende. */
  base: "aprovacao" | "entrega";
};

export type DatasDoCronograma = {
  /* Dia da aprovação (a criação do pedido). */
  aprovacao?: string | null;
  /* Dia prometido de entrega ou retirada. */
  entrega?: string | null;
};

function textoDoMomento(quando: Momento, dias: number): string {
  if (quando === "aprovacao") return "na aprovação";
  if (quando === "entrega") return "na entrega";
  if (quando === "retirada") return "na retirada";
  return `${dias} ${dias === 1 ? "dia" : "dias"} após a entrega`;
}

function vencimentoDoMomento(quando: Momento, dias: number, datas: DatasDoCronograma): { vencimento: string | null; base: "aprovacao" | "entrega" } {
  const aprovacao = diaLocal(datas.aprovacao);
  const entrega = diaLocal(datas.entrega);
  if (quando === "aprovacao") return { vencimento: aprovacao, base: "aprovacao" };
  if (quando === "faturado") return { vencimento: entrega ? somarDias(entrega, dias) : null, base: "entrega" };
  return { vencimento: entrega, base: "entrega" };
}

export function cronograma(condicoes: CondicoesComerciais, total: number, datas: DatasDoCronograma = {}): ParcelaDoCronograma[] {
  const totalCerto = centavos(Math.max(0, total));
  const aprovacao = diaLocal(datas.aprovacao);
  const entrega = diaLocal(datas.entrega);

  if (condicoes.esquema === "a_vista") {
    const momento = vencimentoDoMomento(condicoes.a_vista.quando, condicoes.a_vista.dias, datas);
    return [
      {
        numero: 1,
        rotulo: "À vista",
        valor: totalCerto,
        quando: textoDoMomento(condicoes.a_vista.quando, condicoes.a_vista.dias),
        vencimento: momento.vencimento,
        base: momento.base,
      },
    ];
  }

  if (condicoes.esquema === "entrada_e_saldo") {
    const entradaValor = valorDaEntrada(condicoes, totalCerto);
    const saldoValor = centavos(totalCerto - entradaValor);
    const rotuloDaEntrada =
      condicoes.entrada.tipo === "percentual" ? `Entrada (${condicoes.entrada.valor}%)` : "Entrada";
    const parcelas: ParcelaDoCronograma[] = [
      { numero: 1, rotulo: rotuloDaEntrada, valor: entradaValor, quando: "na aprovação", vencimento: aprovacao, base: "aprovacao" },
    ];
    if (saldoValor <= 0) return parcelas;

    if (condicoes.saldo.quando === "parcelado") {
      const valores = dividirEmParcelas(saldoValor, condicoes.saldo.parcelas);
      valores.forEach((valor, indice) => {
        const dias = condicoes.saldo.intervalo_dias * (indice + 1);
        parcelas.push({
          numero: indice + 2,
          rotulo: `Saldo ${indice + 1} de ${valores.length}`,
          valor,
          quando: `${dias} dias após a aprovação`,
          vencimento: aprovacao ? somarDias(aprovacao, dias) : null,
          base: "aprovacao",
        });
      });
      return parcelas;
    }

    const momento = vencimentoDoMomento(condicoes.saldo.quando, condicoes.saldo.dias, datas);
    parcelas.push({
      numero: 2,
      rotulo: "Saldo",
      valor: saldoValor,
      quando: textoDoMomento(condicoes.saldo.quando, condicoes.saldo.dias),
      vencimento: momento.vencimento,
      base: momento.base,
    });
    return parcelas;
  }

  /* Parcelado em parcelas iguais. */
  const valores = dividirEmParcelas(totalCerto, condicoes.parcelado.parcelas);
  const { primeira, intervalo_dias: intervalo, dias } = condicoes.parcelado;
  return valores.map((valor, indice) => {
    const deslocamento = indice * intervalo;
    if (primeira === "entrega") {
      return {
        numero: indice + 1,
        rotulo: `Parcela ${indice + 1} de ${valores.length}`,
        valor,
        quando: indice === 0 ? "na entrega" : `${deslocamento} dias após a entrega`,
        vencimento: entrega ? somarDias(entrega, deslocamento) : null,
        base: "entrega" as const,
      };
    }
    const diasAposAprovacao = (primeira === "dias" ? dias : 0) + deslocamento;
    return {
      numero: indice + 1,
      rotulo: `Parcela ${indice + 1} de ${valores.length}`,
      valor,
      quando: diasAposAprovacao === 0 ? "na aprovação" : `${diasAposAprovacao} dias após a aprovação`,
      vencimento: aprovacao ? somarDias(aprovacao, diasAposAprovacao) : null,
      base: "aprovacao" as const,
    };
  });
}

/* ------------------------------------------------------------------ */
/* As parcelas diante do que já foi pago                               */

export type SituacaoDaParcela = "paga" | "parcial" | "pendente" | "vencida";

export type ParcelaComSituacao = ParcelaDoCronograma & {
  pago: number;
  situacao: SituacaoDaParcela;
};

/* O dinheiro recebido quita as parcelas na ordem. Uma parcela pendente com
   vencimento no passado está vencida. */
export function situacaoDasParcelas(parcelas: ParcelaDoCronograma[], pago: number, hoje?: string | null): ParcelaComSituacao[] {
  const diaDeHoje = diaLocal(hoje) ?? new Date().toISOString().slice(0, 10);
  let restante = centavos(Math.max(0, pago));
  return parcelas.map((parcela) => {
    const quitado = centavos(Math.min(parcela.valor, restante));
    restante = centavos(restante - quitado);
    let situacao: SituacaoDaParcela;
    if (parcela.valor <= 0 || quitado >= parcela.valor - 0.005) situacao = "paga";
    else if (parcela.vencimento && parcela.vencimento < diaDeHoje) situacao = "vencida";
    else if (quitado > 0) situacao = "parcial";
    else situacao = "pendente";
    return { ...parcela, pago: quitado, situacao };
  });
}

export function proximaParcelaEmAberto(parcelas: ParcelaComSituacao[]): ParcelaComSituacao | null {
  return parcelas.find((parcela) => parcela.situacao !== "paga") ?? null;
}

/* ------------------------------------------------------------------ */
/* As condições escritas, como saem na proposta                        */

function moeda(valor: number) {
  return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(valor || 0).replace(/ /g, " ");
}

function listarEmPortugues(itens: string[]) {
  if (itens.length <= 1) return itens.join("");
  return `${itens.slice(0, -1).join(", ")} ou ${itens[itens.length - 1]}`;
}

export type CondicoesEscritas = {
  /* "Entrada de 50% (R$ 475,04) na aprovação e saldo na entrega." */
  pagamento: string;
  /* "Pix, dinheiro ou cartão de crédito (em até 3x sem juros)." */
  formas: string;
  observacoes: string;
};

export function descreverCondicoes(condicoes: CondicoesComerciais, total: number): CondicoesEscritas {
  const totalCerto = centavos(Math.max(0, total));
  const formas = listarEmPortugues(
    condicoes.formas.map((codigo) => {
      const forma = FORMAS_DE_PAGAMENTO.find((item) => item.codigo === codigo);
      const nome = forma?.curto ?? codigo;
      if (codigo === "credit_card" && condicoes.cartao.parcelas > 1) {
        return `${nome} (em até ${condicoes.cartao.parcelas}x${condicoes.cartao.sem_juros ? " sem juros" : ""})`;
      }
      return nome;
    })
  );

  let pagamento = "";
  if (condicoes.esquema === "a_vista") {
    const { quando, dias } = condicoes.a_vista;
    pagamento =
      quando === "faturado"
        ? `Faturado: pagamento em ${dias} ${dias === 1 ? "dia" : "dias"} após a entrega`
        : `À vista ${textoDoMomento(quando, dias)}`;
  } else if (condicoes.esquema === "entrada_e_saldo") {
    const entrada = valorDaEntrada(condicoes, totalCerto);
    const entradaTexto =
      condicoes.entrada.tipo === "percentual"
        ? `Entrada de ${condicoes.entrada.valor}%${totalCerto > 0 ? ` (${moeda(entrada)})` : ""} na aprovação`
        : `Entrada de ${moeda(entrada)} na aprovação`;
    const { quando, dias, parcelas, intervalo_dias: intervalo } = condicoes.saldo;
    let saldoTexto: string;
    if (quando === "parcelado") {
      const vencimentos = Array.from({ length: parcelas }, (_, indice) => intervalo * (indice + 1));
      saldoTexto = `saldo em ${parcelas} parcelas (${vencimentos.join(", ").replace(/, (\d+)$/, " e $1")} dias após a aprovação)`;
    } else if (quando === "faturado") {
      saldoTexto = `saldo faturado em ${dias} ${dias === 1 ? "dia" : "dias"} após a entrega`;
    } else {
      saldoTexto = `saldo ${textoDoMomento(quando, dias)}`;
    }
    pagamento = `${entradaTexto} e ${saldoTexto}`;
  } else {
    const { parcelas, intervalo_dias: intervalo, primeira, dias } = condicoes.parcelado;
    const valores = dividirEmParcelas(totalCerto, parcelas);
    const valorTexto = totalCerto > 0 ? ` de ${moeda(valores[0])}` : "";
    if (primeira === "entrega") {
      pagamento = `${parcelas} parcelas${valorTexto}: a primeira na entrega e as demais a cada ${intervalo} dias`;
    } else if (primeira === "dias") {
      const vencimentos = Array.from({ length: parcelas }, (_, indice) => dias + intervalo * indice);
      pagamento = `${parcelas} parcelas${valorTexto}, com vencimentos em ${vencimentos.join(", ").replace(/, (\d+)$/, " e $1")} dias após a aprovação`;
    } else {
      pagamento = `${parcelas} parcelas${valorTexto}: a primeira na aprovação e as demais a cada ${intervalo} dias`;
    }
  }

  return {
    pagamento: `${pagamento}.`,
    formas: formas ? `${formas.charAt(0).toUpperCase()}${formas.slice(1)}.` : "",
    observacoes: condicoes.observacoes.trim(),
  };
}

/* Uma linha só, para o WhatsApp e para listas apertadas. */
export function resumirCondicoes(condicoes: CondicoesComerciais, total: number): string {
  const escritas = descreverCondicoes(condicoes, total);
  return [escritas.pagamento, escritas.formas].filter(Boolean).join(" ");
}
