/**
 * Fechas de cobro de una suscripción.
 *
 * Todo el cálculo va en **UTC**. Las fechas se guardan a medianoche UTC y la
 * versión anterior las leía con `getDate()` / `setDate()`, que son locales: en
 * Paraguay (UTC-3) una fecha guardada como 2026-09-02T00:00Z se leía como el
 * día 1, y una suscripción configurada para cobrar el 5 terminaba venciendo
 * el 6.
 */

/** Último día del mes (año/mes en base 0), para no desbordar a 31 de febrero. */
function ultimoDiaDelMes(anio: number, mes: number): number {
  return new Date(Date.UTC(anio, mes + 1, 0)).getUTCDate();
}

/** Arma una fecha UTC recortando el día al último válido de ese mes. */
function enMes(anio: number, mes: number, dia: number): Date {
  // Date.UTC normaliza mes 12 → enero del año siguiente, así que no hace falta
  // manejar el cambio de año a mano.
  const normalizada = new Date(Date.UTC(anio, mes, 1));
  const a = normalizada.getUTCFullYear();
  const m = normalizada.getUTCMonth();
  return new Date(Date.UTC(a, m, Math.min(dia, ultimoDiaDelMes(a, m))));
}

/** Empuja la fecha de a un mes hasta dejarla en el futuro (altas retroactivas). */
function llevarAlFuturo(candidato: Date, diaCobro?: number | null): Date {
  const hoy = new Date();
  hoy.setUTCHours(0, 0, 0, 0);
  let c = candidato;
  while (c <= hoy) {
    c = enMes(c.getUTCFullYear(), c.getUTCMonth() + 1, diaCobro ?? c.getUTCDate());
  }
  return c;
}

/**
 * Piso del primer período: días de servicio que la empresa tiene garantizados
 * antes de recibir su primera factura.
 *
 * Existe porque al dar de alta una empresa la suscripción ya queda activa, y
 * sin este piso el largo del primer ciclo dependía de en qué parte del mes
 * cayera el alta: alguien que se registraba el 2 con cobro el 5 recibía la
 * factura a los 3 días, y uno que se registraba el 30 la recibía a los 5.
 */
export const DIAS_MINIMOS_PRIMER_CICLO = 30;

/**
 * Primer cobro de una suscripción recién creada.
 *
 * Con `diaCobro`: el próximo día fijo que caiga al menos
 * `DIAS_MINIMOS_PRIMER_CICLO` días después del alta. Así nadie paga antes de
 * haber usado el sistema un mes, y desde el segundo ciclo todos quedan
 * alineados al mismo día — que es para lo que existe `dia_cobro`.
 *
 * Sin `diaCobro`: aniversario, un mes exacto.
 */
export function calcularPrimerCobro(fechaInicio: Date, diaCobro?: number | null): Date {
  if (!diaCobro) {
    return llevarAlFuturo(
      enMes(fechaInicio.getUTCFullYear(), fechaInicio.getUTCMonth() + 1, fechaInicio.getUTCDate()),
    );
  }

  const piso = new Date(fechaInicio.getTime() + DIAS_MINIMOS_PRIMER_CICLO * 86_400_000);
  let candidato = enMes(piso.getUTCFullYear(), piso.getUTCMonth(), diaCobro);
  if (candidato < piso) {
    candidato = enMes(piso.getUTCFullYear(), piso.getUTCMonth() + 1, diaCobro);
  }
  return llevarAlFuturo(candidato, diaCobro);
}

/**
 * Cobro siguiente a partir del vencimiento actual: un mes, sin piso.
 *
 * El piso es sólo del primer ciclo. Aplicarlo acá rompería febrero: un ciclo
 * que vence el 05/02 dura 28 días, menos que el piso de 30, y saltaría a abril
 * en vez de marzo.
 */
export function calcularProximoCobro(base: Date, diaCobro?: number | null): Date {
  return llevarAlFuturo(
    enMes(base.getUTCFullYear(), base.getUTCMonth() + 1, diaCobro ?? base.getUTCDate()),
    diaCobro,
  );
}
