/**
 * Cálculo de cuota con interés, agnóstico a la frecuencia de pago.
 *
 * `tasaInteres` es siempre una tasa efectiva ANUAL, sin importar si las cuotas son
 * semanales, quincenales o mensuales. Para 'compuesto' y 'frances' se deriva la tasa
 * del período (`intervaloDias`) por equivalencia de tasas, en vez de asumir que cada
 * cuota dura siempre 30 días — ese supuesto fijo hacía que un plan semanal o quincenal
 * cobrara de facto una tasa anual varias veces mayor a la configurada (ver
 * docs/plan-motor-precios-rentabilidad.md). 'simple' no depende del período: es un
 * recargo fijo de una sola vez sobre el monto financiado, igual que antes.
 */
export type TipoInteres = 'simple' | 'compuesto' | 'frances';

export function calcularMontoCuotaConInteres(
  monto: number,
  cuotas: number,
  tasaInteres: number,
  tipoInteres: TipoInteres,
  intervaloDias: number,
): number {
  if (tasaInteres === 0 || cuotas <= 0) {
    return monto / cuotas;
  }

  switch (tipoInteres) {
    case 'simple':
      return (monto * (1 + tasaInteres / 100)) / cuotas;

    case 'compuesto': {
      const tasaPeriodo = Math.pow(1 + tasaInteres / 100, intervaloDias / 365) - 1;
      if (tasaPeriodo === 0) return monto / cuotas;
      return (monto * (Math.pow(1 + tasaPeriodo, cuotas) * tasaPeriodo)) / (Math.pow(1 + tasaPeriodo, cuotas) - 1);
    }

    case 'frances': {
      const tasaPeriodo = Math.pow(1 + tasaInteres / 100, intervaloDias / 365) - 1;
      if (tasaPeriodo === 0) return monto / cuotas;
      return (monto * (tasaPeriodo * Math.pow(1 + tasaPeriodo, cuotas))) / (Math.pow(1 + tasaPeriodo, cuotas) - 1);
    }

    default:
      return monto / cuotas;
  }
}

export interface CuotaAmortizacion {
  numero: number;
  monto_cuota: number;
  capital: number;
  interes: number;
  saldo: number;
}

/**
 * Redondeo comercial de una cuota: números "limpios" fáciles de comunicar al cliente
 * (igual que `redondearCuota` del frontend de Solicitud de Crédito, para que el monto
 * de cuota se vea igual con o sin plan automático). Solo aplica a PYG — para moneda
 * extranjera el redondeo agresivo a miles no tiene sentido (un monto en USD/BRL queda
 * con centavos, redondeado a 2 decimales).
 */
export function redondearCuotaComercial(monto: number, monedaIso = 'PYG'): number {
  if (monedaIso !== 'PYG') {
    return Math.round(monto * 100) / 100;
  }
  if (monto >= 10000) return Math.floor(monto / 1000) * 1000;
  if (monto >= 1000) return Math.floor(monto / 100) * 100;
  return Math.round(monto);
}

/**
 * Genera la tabla de amortización completa (capital/interés/saldo por cuota), no solo
 * el monto de cuota. El interés de cada período se calcula siempre sobre el saldo
 * REAL restante (no sobre un cronograma teórico), y la última cuota cancela el saldo
 * exacto — así el redondeo comercial de las cuotas intermedias no deja ni un Gs de
 * diferencia sin cobrar, igual que hace cualquier tabla de amortización real.
 */
export function generarTablaAmortizacion(
  monto: number,
  cuotas: number,
  tasaInteres: number,
  tipoInteres: TipoInteres,
  intervaloDias: number,
  monedaIso = 'PYG',
): CuotaAmortizacion[] {
  if (cuotas <= 0 || monto <= 0) return [];

  // PYG no usa centavos: capital/interés se redondean a Gs entero. Moneda extranjera
  // conserva 2 decimales.
  const round2 = (n: number) => (monedaIso === 'PYG' ? Math.round(n) : Math.round(n * 100) / 100);

  if (tasaInteres === 0) {
    // Sin interés: división lineal, la última cuota absorbe el redondeo (comportamiento
    // preexistente, igual al de Solicitud de Crédito sin plan).
    const cuotaBase = redondearCuotaComercial(monto / cuotas, monedaIso);
    const tabla: CuotaAmortizacion[] = [];
    let saldo = monto;
    for (let i = 1; i <= cuotas; i++) {
      const montoCuota = i < cuotas ? cuotaBase : round2(saldo);
      saldo = round2(saldo - montoCuota);
      tabla.push({ numero: i, monto_cuota: montoCuota, capital: montoCuota, interes: 0, saldo: Math.max(0, saldo) });
    }
    return tabla;
  }

  if (tipoInteres === 'simple') {
    // Recargo fijo de una sola vez: no hay saldo que amortice período a período, se
    // reparte el interés total en partes iguales junto con el capital.
    const interesTotal = monto * (tasaInteres / 100);
    const cuotaBase = redondearCuotaComercial((monto + interesTotal) / cuotas, monedaIso);
    const interesPorCuota = round2(interesTotal / cuotas);
    const tabla: CuotaAmortizacion[] = [];
    let saldo = monto + interesTotal;
    for (let i = 1; i <= cuotas; i++) {
      const montoCuota = i < cuotas ? cuotaBase : round2(saldo);
      const interes = i < cuotas ? interesPorCuota : round2(interesTotal - interesPorCuota * (cuotas - 1));
      const capital = round2(montoCuota - interes);
      saldo = round2(saldo - montoCuota);
      tabla.push({ numero: i, monto_cuota: montoCuota, capital, interes, saldo: Math.max(0, saldo) });
    }
    return tabla;
  }

  // Compuesto / francés: amortización por saldo decreciente real.
  const tasaPeriodo = Math.pow(1 + tasaInteres / 100, intervaloDias / 365) - 1;
  const cuotaTeorica = calcularMontoCuotaConInteres(monto, cuotas, tasaInteres, tipoInteres, intervaloDias);
  const cuotaRedondeada = redondearCuotaComercial(cuotaTeorica, monedaIso);

  const tabla: CuotaAmortizacion[] = [];
  let saldo = monto;
  for (let i = 1; i <= cuotas; i++) {
    const interes = round2(saldo * tasaPeriodo);
    if (i < cuotas) {
      const capital = round2(cuotaRedondeada - interes);
      saldo = round2(saldo - capital);
      tabla.push({ numero: i, monto_cuota: cuotaRedondeada, capital, interes, saldo });
    } else {
      // Última cuota: cancela el saldo exacto restante (incluye el redondeo acumulado).
      const capital = saldo;
      const montoCuota = round2(capital + interes);
      tabla.push({ numero: i, monto_cuota: montoCuota, capital, interes, saldo: 0 });
    }
  }
  return tabla;
}
