import 'dotenv/config';
import { PrismaClient, Prisma } from '@prisma/client';

/**
 * Fija el saldo de una cuenta de tesorería a una fecha de corte, contra el saldo real
 * del extracto bancario.
 *
 * El problema que resuelve: se puso un movimiento de ajuste con monto fijo para cuadrar
 * abril contra el extracto, y quedó desfasado apenas siguieron cargando movimientos con
 * fecha anterior al corte. Con DOBA fueron Gs. 130.084.390 en tres semanas, porque están
 * cargando el histórico del extracto de abril recién ahora.
 *
 * En vez de un monto fijo, este script CALCULA la diferencia en el momento y reescribe
 * el ajuste. Es re-ejecutable: se corre cada vez que terminan un tramo de carga y el
 * corte vuelve a quedar en el saldo real. Nunca hardcodea el monto.
 *
 * Uso:
 *   pnpm ajustar:saldo -- --cuenta=<uuid> --fecha=2026-04-30 --saldo=14794339 --dry
 *   pnpm ajustar:saldo -- --cuenta=<uuid> --fecha=2026-04-30 --saldo=14794339
 */

const prisma = new PrismaClient();
const arg = (n: string) => process.argv.slice(2).find((a) => a.startsWith(`--${n}=`))?.split('=').slice(1).join('=');
const DRY = process.argv.includes('--dry');
const fmt = (n: number) => n.toLocaleString('es-PY', { maximumFractionDigits: 0 });

const ORIGEN = 'AJUSTE_SALDO';

async function main() {
  const cuentaId = arg('cuenta');
  const fechaCorte = arg('fecha');
  const saldoObjetivo = Number(arg('saldo'));

  if (!cuentaId || !fechaCorte || !Number.isFinite(saldoObjetivo)) {
    throw new Error('Uso: --cuenta=<uuid> --fecha=YYYY-MM-DD --saldo=<numero> [--dry]');
  }
  // Se toma todo el día del corte: un movimiento fechado el 30/04 entra en abril.
  const hasta = new Date(`${fechaCorte}T23:59:59.999Z`);

  const cuenta = await prisma.tes_cuentas.findUnique({
    where: { id: cuentaId },
    select: { id: true, empresa_id: true, nombre: true, moneda: true, saldo_inicial: true },
  });
  if (!cuenta) throw new Error(`Cuenta ${cuentaId} no encontrada`);

  // El ajuste anterior se excluye del cálculo: si se contara, cada corrida lo
  // arrastraría y el resultado dependería de cuántas veces se corrió antes.
  const movs = await prisma.tes_movimientos.findMany({
    where: {
      cuenta_id: cuentaId,
      estado: { not: 'ANULADO' },
      fecha: { lte: hasta },
      // `not` sobre una columna nullable descarta los NULL (NULL <> 'X' es NULL, no
      // true), y los movimientos cargados a mano no tienen origen_tipo. Sin el OR se
      // perdían: con DOBA eran Gs. 42.957.643 que quedaban fuera del cálculo.
      OR: [{ origen_tipo: null }, { origen_tipo: { not: ORIGEN } }],
    },
    select: { tipo: true, monto: true },
  });

  const neto = movs.reduce((s, m) => s + (m.tipo === 'INGRESO' ? Number(m.monto) : -Number(m.monto)), 0);
  const saldoSinAjuste = Number(cuenta.saldo_inicial ?? 0) + neto;
  const ajusteNecesario = saldoObjetivo - saldoSinAjuste;

  const previos = await prisma.tes_movimientos.findMany({
    where: { cuenta_id: cuentaId, origen_tipo: ORIGEN, estado: { not: 'ANULADO' }, fecha: { lte: hasta } },
    select: { id: true, tipo: true, monto: true, fecha: true },
  });
  const ajusteActual = previos.reduce((s, m) => s + (m.tipo === 'INGRESO' ? Number(m.monto) : -Number(m.monto)), 0);

  console.log(`Cuenta        : ${cuenta.nombre} (${cuenta.moneda})`);
  console.log(`Corte         : ${fechaCorte}`);
  console.log(`Movimientos   : ${movs.length} (sin contar ajustes previos)`);
  console.log('');
  console.log(`Saldo sin ajuste : ${fmt(saldoSinAjuste)}`);
  console.log(`Saldo objetivo   : ${fmt(saldoObjetivo)}`);
  console.log(`Ajuste necesario : ${fmt(ajusteNecesario)}`);
  console.log(`Ajuste vigente   : ${fmt(ajusteActual)}  (${previos.length} movimiento/s)`);
  console.log(`Desfasaje        : ${fmt(ajusteNecesario - ajusteActual)}`);

  if (Math.abs(ajusteNecesario - ajusteActual) < 0.01) {
    console.log('\nEl corte ya está cuadrado. No hay nada que hacer.');
    return;
  }
  if (DRY) {
    console.log('\nDRY-RUN: no se escribió nada.');
    return;
  }

  await prisma.$transaction(async (tx) => {
    // Se anulan los ajustes previos en vez de editarlos, para que quede el rastro de
    // cuánto se había forzado antes y cuándo.
    if (previos.length > 0) {
      await tx.tes_movimientos.updateMany({
        where: { id: { in: previos.map((p) => p.id) } },
        data: {
          estado: 'ANULADO',
          anulado_en: new Date(),
          motivo_anulacion: `Reemplazado por recálculo de corte al ${fechaCorte}`,
        },
      });
    }

    if (Math.abs(ajusteNecesario) >= 0.01) {
      await tx.tes_movimientos.create({
        data: {
          empresa_id: cuenta.empresa_id,
          cuenta_id: cuentaId,
          tipo: ajusteNecesario >= 0 ? 'INGRESO' : 'EGRESO',
          estado: 'CONFIRMADO',
          fecha: new Date(`${fechaCorte}T00:00:00.000Z`),
          monto: new Prisma.Decimal(Math.abs(ajusteNecesario)),
          descripcion: `Ajuste de saldo al ${fechaCorte} — saldo bancario según extracto`,
          referencia: `CORTE-${fechaCorte}`,
          origen_tipo: ORIGEN,
        },
      });
    }

    // saldo_actual se mantiene por incrementos, así que hay que moverlo por el delta;
    // recalcularlo entero acá pisaría movimientos posteriores al corte.
    await tx.tes_cuentas.update({
      where: { id: cuentaId },
      data: { saldo_actual: { increment: new Prisma.Decimal(ajusteNecesario - ajusteActual) } },
    });
  });

  console.log(`\nListo. Ajuste fijado en ${fmt(ajusteNecesario)}; saldo_actual movido en ${fmt(ajusteNecesario - ajusteActual)}.`);
}

main()
  .then(() => prisma.$disconnect())
  .catch(async (e) => { console.error(e.message ?? e); await prisma.$disconnect(); process.exit(1); });
