/**
 * One-shot: importa movimientos bancarios de Doba S.A desde el extracto Excel.
 *
 *  - Busca la cuenta `tes_cuentas` por `numero_cuenta` dentro de la empresa.
 *  - Crea (idempotente) dos categorías genéricas para la importación.
 *  - Por cada fila (todas las hojas) crea `tes_movimientos` CONFIRMADO + 1 detalle.
 *  - Idempotencia: `origen_tipo='IMPORT_EXTRACTO'` + `referencia=<nro_comprobante>`
 *    + `cuenta_id`. Re-correr no duplica.
 *  - Actualiza `saldo_actual` de la cuenta al saldo del último movimiento procesado.
 *
 * Uso:
 *   npx ts-node scripts/migrate-doba-movimientos-bancarios.ts \
 *     --empresa=3b327470-82de-4c8b-bc6f-070f38a4f238 \
 *     --numero-cuenta=22-3274482 \
 *     --excel="/home/derlis-dacosta/proyectos/doba S.A/movimientos_bancarios_doba.xlsx" \
 *     [--dry-run]
 */
import { PrismaClient, Prisma } from '@prisma/client';
import * as XLSX from 'xlsx';

interface Movimiento {
  fecha: Date;
  comprobante: string;
  descripcion: string;
  tipo: 'INGRESO' | 'EGRESO';
  monto: number;
  saldo: number;
  hoja: string;
}

function parseArgs() {
  const out: any = { dryRun: false };
  for (const arg of process.argv.slice(2)) {
    if (arg === '--dry-run') out.dryRun = true;
    else if (arg.startsWith('--empresa=')) out.empresa = arg.slice(10);
    else if (arg.startsWith('--numero-cuenta=')) out.numeroCuenta = arg.slice(16);
    else if (arg.startsWith('--excel=')) out.excel = arg.slice(8);
  }
  if (!out.empresa || !out.numeroCuenta || !out.excel) {
    throw new Error('Faltan --empresa --numero-cuenta --excel');
  }
  return out;
}

function parseFechaDMY(s: string): Date {
  // DD/MM/YYYY → Date (UTC midnight)
  const [d, m, y] = s.split('/').map(Number);
  return new Date(Date.UTC(y, m - 1, d));
}

function leerExcel(path: string): Movimiento[] {
  const wb = XLSX.readFile(path);
  const movs: Movimiento[] = [];
  for (const hoja of wb.SheetNames) {
    const ws = wb.Sheets[hoja];
    const rows = XLSX.utils.sheet_to_json<any[]>(ws, {
      header: 1,
      raw: true,
      defval: null,
      blankrows: false,
    });
    // Header en index 10, datos desde 11
    for (let i = 11; i < rows.length; i++) {
      const r = rows[i];
      if (!r || !r[1] || !r[2]) continue;
      const fechaMov = String(r[1]).trim();
      const comprobante = String(r[2]).trim();
      const descripcion = String(r[3] ?? '').trim();
      const debito = Number(r[4] ?? 0);
      const credito = Number(r[5] ?? 0);
      const saldo = Number(r[6] ?? 0);

      if (!/^\d{2}\/\d{2}\/\d{4}$/.test(fechaMov)) continue;
      if (debito <= 0 && credito <= 0) continue;

      movs.push({
        fecha: parseFechaDMY(fechaMov),
        comprobante,
        descripcion: descripcion.slice(0, 500),
        tipo: credito > 0 ? 'INGRESO' : 'EGRESO',
        monto: credito > 0 ? credito : debito,
        saldo,
        hoja,
      });
    }
  }
  return movs;
}

async function ensureCategoria(
  prisma: PrismaClient,
  empresaId: string,
  tipo: 'INGRESO' | 'EGRESO',
): Promise<string> {
  const codigo = `IMPORT_EXTRACTO_${tipo}`;
  const existing = await prisma.tes_categorias.findFirst({
    where: { empresa_id: empresaId, codigo_interno: codigo },
    select: { id: true },
  });
  if (existing) return existing.id;
  const created = await prisma.tes_categorias.create({
    data: {
      empresa_id: empresaId,
      codigo_interno: codigo,
      nombre: `Importación extracto - ${tipo === 'INGRESO' ? 'Ingreso' : 'Egreso'}`,
      descripcion: 'Categoría genérica para movimientos importados desde extracto bancario',
      tipo,
      activo: true,
    },
    select: { id: true },
  });
  return created.id;
}

async function main() {
  const { empresa, numeroCuenta, excel, dryRun } = parseArgs();
  const prisma = new PrismaClient();

  try {
    const cuenta = await prisma.tes_cuentas.findFirst({
      where: { empresa_id: empresa, numero_cuenta: numeroCuenta },
      select: { id: true, nombre: true, saldo_actual: true },
    });
    if (!cuenta) throw new Error(`Cuenta ${numeroCuenta} no existe para la empresa`);
    console.log(`Cuenta: ${cuenta.nombre} (${cuenta.id}) | saldo_actual: ${cuenta.saldo_actual}`);

    const movs = leerExcel(excel);
    console.log(`Excel: ${movs.length} movimientos válidos`);
    const porHoja = movs.reduce<Record<string, number>>((acc, m) => {
      acc[m.hoja] = (acc[m.hoja] ?? 0) + 1;
      return acc;
    }, {});
    console.log('Por hoja:', porHoja);
    const sumIng = movs.filter((m) => m.tipo === 'INGRESO').reduce((s, m) => s + m.monto, 0);
    const sumEgr = movs.filter((m) => m.tipo === 'EGRESO').reduce((s, m) => s + m.monto, 0);
    console.log(`Ingresos: ${sumIng.toLocaleString()} | Egresos: ${sumEgr.toLocaleString()}`);
    console.log(`Saldo último mov: ${movs[movs.length - 1]?.saldo.toLocaleString()}`);

    if (dryRun) {
      console.log('--- DRY RUN: nada se persiste ---');
      console.log('Primeros 3:', movs.slice(0, 3));
      console.log('Últimos 3:', movs.slice(-3));
      await prisma.$disconnect();
      return;
    }

    const catIngreso = await ensureCategoria(prisma, empresa, 'INGRESO');
    const catEgreso = await ensureCategoria(prisma, empresa, 'EGRESO');
    console.log(`Categorías OK (ingreso=${catIngreso}, egreso=${catEgreso})`);

    const yaExistentes = await prisma.tes_movimientos.findMany({
      where: { cuenta_id: cuenta.id, origen_tipo: 'IMPORT_EXTRACTO' },
      select: { referencia: true },
    });
    const refsExistentes = new Set(yaExistentes.map((m) => m.referencia));
    console.log(`Ya importados previamente: ${refsExistentes.size}`);

    let creados = 0;
    let saltados = 0;

    for (const m of movs) {
      if (refsExistentes.has(m.comprobante)) {
        saltados++;
        continue;
      }
      await prisma.$transaction(async (tx) => {
        const mov = await tx.tes_movimientos.create({
          data: {
            empresa_id: empresa,
            cuenta_id: cuenta.id,
            tipo: m.tipo,
            estado: 'CONFIRMADO',
            fecha: m.fecha,
            monto: new Prisma.Decimal(m.monto),
            descripcion: m.descripcion,
            referencia: m.comprobante,
            origen_tipo: 'IMPORT_EXTRACTO',
            confirmado_en: new Date(),
          },
          select: { id: true },
        });
        await tx.tes_movimiento_det.create({
          data: {
            movimiento_id: mov.id,
            categoria_id: m.tipo === 'INGRESO' ? catIngreso : catEgreso,
            monto: new Prisma.Decimal(m.monto),
            descripcion: m.descripcion,
          },
        });
      });
      creados++;
    }

    // Recalcular saldo_actual desde TODOS los movimientos confirmados de la cuenta
    // (no solo los importados — así suma los previos del usuario en prod).
    const agregados = await prisma.tes_movimientos.groupBy({
      by: ['tipo'],
      where: { cuenta_id: cuenta.id, estado: 'CONFIRMADO' },
      _sum: { monto: true },
    });
    const sumPorTipo = new Map(
      agregados.map((g) => [g.tipo, Number(g._sum.monto ?? 0)]),
    );
    const cuentaFull = await prisma.tes_cuentas.findUnique({
      where: { id: cuenta.id },
      select: { saldo_inicial: true },
    });
    const saldoInicial = Number(cuentaFull?.saldo_inicial ?? 0);
    const saldoCalculado =
      saldoInicial +
      (sumPorTipo.get('INGRESO') ?? 0) -
      (sumPorTipo.get('EGRESO') ?? 0);

    await prisma.tes_cuentas.update({
      where: { id: cuenta.id },
      data: { saldo_actual: new Prisma.Decimal(saldoCalculado) },
    });

    console.log(`✅ Creados: ${creados} | Saltados (ya existían): ${saltados}`);
    console.log(
      `✅ saldo_actual recalculado: ${saldoCalculado.toLocaleString()} ` +
        `(inicial=${saldoInicial.toLocaleString()} + ingresos=${(sumPorTipo.get('INGRESO') ?? 0).toLocaleString()} - egresos=${(sumPorTipo.get('EGRESO') ?? 0).toLocaleString()})`,
    );
  } finally {
    await prisma.$disconnect();
  }
}

main().catch((e) => {
  console.error('❌ Error:', e);
  process.exit(1);
});
