import { Prisma } from '@prisma/client';

/**
 * Medio del recibo que cancela consumo de funcionario descontado en el
 * finiquito. Como DESCUENTO_PLANILLA, no mueve plata: se compensa contra lo
 * que se le paga. Contablemente es distinto: el pago del finiquito netea los
 * descuentos contra los gastos (el asiento sale de tesorería por lo pagado),
 * así que nadie acredita la cuenta puente. Este recibo debita SUELDOS_JORNALES
 * en su lugar, y entre los dos asientos el gasto queda completo.
 */
export const MEDIO_DESCUENTO_FINIQUITO = 'DESCUENTO_FINIQUITO';

const r2 = (n: number) => Math.round((n + Number.EPSILON) * 100) / 100;
const num = (v: unknown) => {
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
};

export interface CobroConsumoParams {
  empresaId: string;
  empleado: { cliente_id: string; sucursal_id: string };
  /** Qué cuenta por cobrar cancelar y por cuánto. */
  items: { cuentaCobrarId: string; monto: number }[];
  medio: string;
  observacion: string;
  fecha: Date;
  userId?: string;
}

export type GenerarNumeroRecibo = (
  tx: Prisma.TransactionClient,
  empresaId: string,
  sucursalId: string,
) => Promise<string>;

/**
 * Cancela facturas del funcionario y emite el recibo que lo documenta, dentro
 * de la transacción de quien llama (cierre de liquidación o pago de finiquito).
 *
 * Nunca cobra más que el saldo de cada cuenta: si entre el cálculo y este
 * momento alguien la cobró por otra vía, aplica lo que quede. Devuelve null si
 * no quedó nada para cobrar.
 */
export async function emitirCobroConsumo(
  tx: Prisma.TransactionClient,
  generarNumero: GenerarNumeroRecibo,
  p: CobroConsumoParams,
): Promise<{ reciboId: string; totalCobrado: number } | null> {
  const cuentas = await tx.cuentas_cobrar.findMany({
    where: { id: { in: p.items.map((i) => i.cuentaCobrarId) }, empresa_id: p.empresaId },
    select: { id: true, factura_venta_id: true, saldo_pendiente: true },
  });
  const porId = new Map(cuentas.map((c) => [c.id, c]));

  const aplicados: { cuentaId: string; facturaId: string | null; monto: number; saldoPrevio: number }[] = [];
  for (const item of p.items) {
    const cuenta = porId.get(item.cuentaCobrarId);
    if (!cuenta) continue;
    const saldoPrevio = num(cuenta.saldo_pendiente);
    const aCobrar = r2(Math.min(item.monto, saldoPrevio));
    if (aCobrar <= 0) continue;

    const nuevoSaldo = r2(saldoPrevio - aCobrar);
    await tx.cuentas_cobrar.update({
      where: { id: cuenta.id },
      data: {
        saldo_pendiente: nuevoSaldo,
        estado: nuevoSaldo <= 0 ? 'cobrada' : 'pendiente',
        updated_at: new Date(),
      },
    });
    aplicados.push({ cuentaId: cuenta.id, facturaId: cuenta.factura_venta_id, monto: aCobrar, saldoPrevio });
  }

  if (!aplicados.length) return null;

  const totalCobrado = r2(aplicados.reduce((acc, a) => acc + a.monto, 0));
  const numeroRecibo = await generarNumero(tx, p.empresaId, p.empleado.sucursal_id);
  const recibo = await tx.recibos_cobro.create({
    data: {
      empresa_id: p.empresaId,
      sucursal_id: p.empleado.sucursal_id,
      cliente_id: p.empleado.cliente_id,
      usuario_id: p.userId,
      numero_recibo: numeroRecibo,
      fecha_emision: p.fecha,
      monto_total: totalCobrado,
      // Los totales que lee el asiento (integrarReciboMulti). Sin ellos salía
      // con haber 0, no cuadraba y el recibo quedaba sin contabilizar.
      total_facturas: totalCobrado,
      total_pagado: totalCobrado,
      estado: 'emitido',
      modo: 'MULTI',
      observacion: p.observacion,
      medios_pago_multi: { create: [{ medio: p.medio, monto: totalCobrado }] },
    },
    select: { id: true },
  });

  // Las facturas imputadas. Solo las que tienen factura asociada: una cuenta
  // por cobrar suelta (sin factura) igual baja su saldo arriba.
  for (const [i, a] of aplicados.entries()) {
    if (!a.facturaId) continue;
    await tx.recibo_cobro_facturas.create({
      data: {
        recibo_cobro_id: recibo.id,
        factura_cab_id: a.facturaId,
        cuota_id: a.cuentaId,
        monto_saldo_previo: a.saldoPrevio,
        monto_pagado: a.monto,
        orden: i,
      },
    });
  }

  return { reciboId: recibo.id, totalCobrado };
}
