import { db } from "./db";
import { pagosServicios, gastosServicios, servicios } from "./schema";
import { eq } from "drizzle-orm";
import { parseServicePaymentNotes } from "./service-payment-meta";

/**
 * Distributes all pagos_servicios (global payments) proportionally
 * across gastos_servicios entries, updating montoPagado in each.
 *
 * Also calculates porcentaje_cubierto for each payment:
 * what % of the total overdue debt at that moment was covered.
 *
 * Logic:
 * 1. Process payments chronologically
 * 2. For each payment, compute total outstanding debt for entries
 *    whose vencimiento <= payment date
 * 3. Distribute payment across oldest debts first
 * 4. Set porcentaje_cubierto = monto_pago / total_outstanding * 100
 */
export async function syncPagosServicios() {
  // Get all active servicios with their percentages
  const serviciosData = await db
    .select({ id: servicios.id, porcentaje: servicios.porcentaje })
    .from(servicios)
    .where(eq(servicios.activo, 1));

  // Get all gastos entries
  const gastosData = await db.select().from(gastosServicios);

  // Get all global payments (sorted chronologically)
  const pagosData = await db
    .select()
    .from(pagosServicios)
    .orderBy(pagosServicios.fecha);

  const porcentajeMap = Object.fromEntries(
    serviciosData.map((s) => [s.id, s.porcentaje / 100])
  );
  const activeServicioIds = new Set(serviciosData.map((s) => s.id));

  // Reset all montoPagado to 0 first
  await db.update(gastosServicios).set({ montoPagado: 0, fechaPago: null, fechaCompletado: null });

  // Build list of (gastoEntry, amountOwed) sorted by period
  // Only include entries for active servicios
  const gastosWithDebt = gastosData
    .filter((g) => activeServicioIds.has(g.servicioId))
    .map((g) => ({
      ...g,
      pct: porcentajeMap[g.servicioId] ?? 1,
      owes: g.montoFacturado * (porcentajeMap[g.servicioId] ?? 1),
      paid: 0,
      lastPagoFecha: null as string | null,
      completadoFecha: null as string | null,
    }))
    .sort((a, b) => a.periodo.localeCompare(b.periodo) || a.id - b.id);

  // Get vencimiento date for each gasto (day 5 of the period's month)
  function getVencimiento(periodo: string): string {
    const [anio, mes] = periodo.split("-").map(Number);
    const ultimoDia = new Date(anio, mes, 0).getDate();
    const diaVenc = Math.min(5, ultimoDia);
    return `${periodo}-${String(diaVenc).padStart(2, "0")}`;
  }

  // Distribute each payment chronologically across outstanding debt
  // and calculate porcentaje_cubierto
  for (const pago of pagosData) {
    const { meta } = parseServicePaymentNotes(pago.notas);
    const eligibleGastos = gastosWithDebt.filter((gasto) => {
      if (meta.paymentMode !== "specific") return true;
      return meta.serviceId ? gasto.servicioId === meta.serviceId : true;
    });

    // Calculate total outstanding debt for entries whose vencimiento <= payment date
    let totalDeudaVencida = 0;
    for (const gasto of eligibleGastos) {
      const vencimiento = getVencimiento(gasto.periodo);
      if (vencimiento <= pago.fecha) {
        const debtLeft = gasto.owes - gasto.paid;
        if (debtLeft > 0) {
          totalDeudaVencida += debtLeft;
        }
      }
    }

    // Calculate porcentaje_cubierto
    const porcentajeCubierto = totalDeudaVencida > 0
      ? Math.min(100, Math.round((pago.monto / totalDeudaVencida) * 10000) / 100)
      : 0;

    // Update this payment's porcentaje_cubierto in DB
    await db
      .update(pagosServicios)
      .set({ porcentajeCubierto })
      .where(eq(pagosServicios.id, pago.id));

    // Distribute payment across debts (oldest first)
    let remaining = pago.monto;
    for (const gasto of eligibleGastos) {
      if (remaining <= 0) break;

      const vencimiento = getVencimiento(gasto.periodo);
      if (vencimiento > pago.fecha) continue; // Not overdue yet at payment time

      const debtLeft = gasto.owes - gasto.paid;
      if (debtLeft <= 0) continue;

      const toPay = Math.min(debtLeft, remaining);
      gasto.paid += toPay;
      gasto.lastPagoFecha = pago.fecha;
      // If this payment completes the entry (covers 100% of corresponde)
      if (gasto.paid >= gasto.owes && !gasto.completadoFecha) {
        gasto.completadoFecha = pago.fecha;
      }
      remaining -= toPay;
    }
  }

  // Update each gasto entry with its computed montoPagado and fechaPago
  for (const gasto of gastosWithDebt) {
    if (gasto.paid > 0) {
      await db
        .update(gastosServicios)
        .set({
          montoPagado: Math.round(gasto.paid * 100) / 100,
          fechaPago: gasto.lastPagoFecha ?? null,
          fechaCompletado: gasto.completadoFecha ?? null,
          updatedAt: new Date(),
        })
        .where(eq(gastosServicios.id, gasto.id));
    }
  }
}
