import { saldoDeCuenta } from './saldo-cuenta.util';

/**
 * Deja `cuentas_cobrar` y `clientes.saldo_pendiente` alineados con el estado real
 * de las cuotas de una factura.
 *
 * Todo flujo que mueva `factura_cuotas` tiene que llamar a esto dentro de su
 * misma transacción: el cobro, su anulación, la aplicación y reversión de notas
 * de crédito, la refinanciación y la anulación de la factura. El que no lo hace
 * deja la cuenta reclamando una deuda ya saldada, o perdonando una que sigue viva.
 *
 * Recalcula desde las cuotas en lugar de ajustar por diferencia: así una cuenta
 * que venía desalineada se corrige sola en el próximo movimiento, y llamarlo dos
 * veces seguidas no descuadra nada.
 *
 * Nació como método privado de RecibosService (flujo multi-factura), único lugar
 * que lo hacía bien; acá queda compartido para el resto.
 */

/** Lo mínimo que necesita de Prisma: sirve tanto el cliente como una transacción. */
export interface TxSincronizacion {
  cuentas_cobrar: {
    findFirst(args: any): Promise<any>;
    update(args: any): Promise<any>;
  };
  factura_cuotas: {
    findMany(args: any): Promise<any[]>;
  };
  $executeRaw(strings: TemplateStringsArray, ...values: any[]): Promise<number>;
}

export async function sincronizarCuentaCobrar(tx: TxSincronizacion, facturaId: string): Promise<void> {
  const cuenta = await tx.cuentas_cobrar.findFirst({
    where: { factura_venta_id: facturaId },
    select: { id: true, cliente_id: true, saldo_pendiente: true, estado: true },
  });
  if (!cuenta) return;

  const cuotas = await tx.factura_cuotas.findMany({
    where: { factura_cab_id: facturaId },
    select: { saldo_pendiente: true, dmoncuota: true, estado: true },
  });

  const { saldo: nuevoSaldo, estado } = saldoDeCuenta(cuotas);
  const saldoAnterior = Number(cuenta.saldo_pendiente ?? 0);
  const delta = +(nuevoSaldo - saldoAnterior).toFixed(2);
  if (Math.abs(delta) < 0.005) return;

  // 'anulada' es un estado terminal: la cuenta de una factura anulada no vuelve
  // a 'pagado' ni a 'pendiente' porque su saldo llegue a cero. Solo se le
  // corrige el saldo. Así el orden de las escrituras deja de importar y quien
  // anula puede marcar la cuenta antes o después de sincronizar.
  const anulada = ['anulada', 'anulado'].includes(String(cuenta.estado ?? '').toLowerCase());

  await tx.cuentas_cobrar.update({
    where: { id: cuenta.id },
    data: anulada
      ? { saldo_pendiente: nuevoSaldo, updated_at: new Date() }
      : { saldo_pendiente: nuevoSaldo, estado, updated_at: new Date() },
  });

  // El saldo del cliente acumula varias facturas: se mueve por la diferencia, no
  // se pisa. GREATEST evita dejarlo negativo si algún saldo viejo estaba mal.
  if (cuenta.cliente_id) {
    await tx.$executeRaw`
      UPDATE clientes
      SET saldo_pendiente = GREATEST(0, COALESCE(saldo_pendiente, 0) + ${delta}),
          updated_at = NOW()
      WHERE id = ${cuenta.cliente_id}::uuid
    `;
  }
}
