/**
 * Estado de cuenta de una venta: saldo, vencido, días de atraso y mora estimada.
 *
 * Una cuota es exigible desde su vencimiento; las de HITO desde que el hito se
 * cumplió, y las de POSESION desde que se les fija fecha. Mientras no son
 * exigibles no están vencidas aunque tengan saldo (se pueden pagar por adelantado).
 *
 * La mora es interés simple sobre el saldo vencido: saldo × tasa mensual / 30 × días.
 * Es una estimación para la gestión de cobro; en v1 no se factura sola.
 */

export interface CuotaEstado {
  id: string;
  nro: number;
  tipo: string;
  descripcion: string | null;
  fecha_vencimiento: string | null;
  hito_id: string | null;
  monto: number;
  monto_cobrado: number;
  estado: string;
}

export interface CuotaConMora extends CuotaEstado {
  saldo: number;
  exigible_desde: string | null;
  vencida: boolean;
  dias_atraso: number;
  mora_estimada: number;
}

const dias = (desde: string, hasta: string) =>
  Math.round((Date.parse(`${hasta}T00:00:00Z`) - Date.parse(`${desde}T00:00:00Z`)) / 86_400_000);

export function calcularEstadoCuenta(
  cuotas: CuotaEstado[],
  opts: {
    hoy: string;
    hitosCumplidos: Map<string, string>;
    tasaMoraMensual: number;
    decimales: number;
    precio: number;
  },
) {
  const r = (n: number) => Math.round(n * 10 ** opts.decimales) / 10 ** opts.decimales;
  const detalle: CuotaConMora[] = cuotas
    .filter((c) => c.estado !== 'ANULADA')
    .sort((a, b) => a.nro - b.nro)
    .map((c) => {
      const saldo = r(Number(c.monto) - Number(c.monto_cobrado));
      const exigible = c.fecha_vencimiento ?? (c.hito_id ? (opts.hitosCumplidos.get(c.hito_id) ?? null) : null);
      const atraso = exigible && saldo > 0 ? Math.max(0, dias(exigible, opts.hoy)) : 0;
      return {
        ...c,
        saldo,
        exigible_desde: exigible,
        vencida: atraso > 0,
        dias_atraso: atraso,
        mora_estimada: r((saldo * (opts.tasaMoraMensual / 100) * atraso) / 30),
      };
    });

  const cobrado = r(detalle.reduce((a, c) => a + Number(c.monto_cobrado), 0));
  // Precio menos lo cobrado, no la suma de cuotas: antes del boleto sólo existe la
  // cuota de seña y el saldo de la venta es todo el precio.
  const saldo = r(opts.precio - cobrado);
  const vencidas = detalle.filter((c) => c.vencida);
  const proxima = detalle.find((c) => c.saldo > 0 && !c.vencida && c.exigible_desde) ?? null;
  return {
    cuotas: detalle,
    resumen: {
      precio: opts.precio,
      cobrado,
      saldo,
      pct_pagado: opts.precio > 0 ? Math.round((cobrado / opts.precio) * 10000) / 100 : 0,
      vencido: r(vencidas.reduce((a, c) => a + c.saldo, 0)),
      cuotas_vencidas: vencidas.length,
      dias_atraso_max: vencidas.reduce((a, c) => Math.max(a, c.dias_atraso), 0),
      mora_estimada: r(vencidas.reduce((a, c) => a + c.mora_estimada, 0)),
      proxima: proxima
        ? { nro: proxima.nro, descripcion: proxima.descripcion, fecha: proxima.exigible_desde, saldo: proxima.saldo }
        : null,
    },
  };
}
