/**
 * One-shot: migra el plan de cuentas de Doba S.A desde Excel.
 *
 * Estrategia (soft replace):
 *  1. Cuentas existentes de la empresa con is_sistema=false:
 *     - codigo => "OLD-<codigo>", active=false
 *     - Conserva FKs (asientos / mapeos quedan auditables).
 *  2. Inserta las cuentas del Excel con jerarquía por prefijo de código.
 *  3. acepta_movimientos = true sólo en hojas (sin hijos).
 *
 * Uso:
 *   npx ts-node scripts/migrate-doba-plan-cuentas.ts \
 *     --empresa=3b327470-82de-4c8b-bc6f-070f38a4f238 \
 *     --excel="/home/derlis-dacosta/proyectos/doba S.A/PLAN DE CUENTAS NOVASIS.xlsx" \
 *     [--dry-run | --verify]
 *
 * Modo --verify: compara el Excel con la BD (empresa objetivo) sin escribir.
 *   - Lista códigos en Excel que NO están en la BD (faltantes).
 *   - Lista códigos en la BD (no-sistema, activos) que NO están en el Excel (extras).
 *   - Lista códigos del Excel descartados (raíz desconocida o formato inválido).
 */
import { PrismaClient } from '@prisma/client';
import * as XLSX from 'xlsx';

type Tipo = 'ACTIVO' | 'PASIVO' | 'PATRIMONIO' | 'INGRESO' | 'COSTO' | 'GASTO';
type Naturaleza = 'DEUDORA' | 'ACREEDORA';

interface CuentaExcel {
  codigo: string;
  descripcion: string;
  nivel: number;
  tipo: Tipo;
  naturaleza: Naturaleza;
}

const TIPO_POR_RAIZ: Record<string, Tipo> = {
  '1': 'ACTIVO',
  '2': 'PASIVO',
  '3': 'PATRIMONIO',
  '4': 'INGRESO',
  '5': 'COSTO',
  '6': 'PATRIMONIO',
  '7': 'INGRESO',
  '8': 'INGRESO',
  '9': 'PATRIMONIO',
  '10': 'GASTO',
  '11': 'GASTO',
  '12': 'PATRIMONIO',
  '13': 'GASTO',
  '14': 'GASTO',
  '15': 'GASTO',
  '16': 'PATRIMONIO',
  '17': 'INGRESO',
  '18': 'PATRIMONIO',
  '19': 'GASTO',
  '20': 'PATRIMONIO',
};

function naturalezaDe(tipo: Tipo): Naturaleza {
  return tipo === 'ACTIVO' || tipo === 'COSTO' || tipo === 'GASTO'
    ? 'DEUDORA'
    : 'ACREEDORA';
}

function parseArgs(): { empresa: string; excel: string; dryRun: boolean; verify: boolean } {
  const out: any = { dryRun: false, verify: false };
  for (const arg of process.argv.slice(2)) {
    if (arg === '--dry-run') out.dryRun = true;
    else if (arg === '--verify') out.verify = true;
    else if (arg.startsWith('--empresa=')) out.empresa = arg.slice('--empresa='.length);
    else if (arg.startsWith('--excel=')) out.excel = arg.slice('--excel='.length);
  }
  if (!out.empresa || !out.excel) {
    throw new Error('Faltan --empresa=<uuid> y --excel=<path>');
  }
  return out;
}

interface Descartada {
  codigo: string;
  descripcion: string;
  motivo: string;
  fila: number;
}

function leerExcel(path: string): { cuentas: CuentaExcel[]; descartadas: Descartada[] } {
  const wb = XLSX.readFile(path);
  const ws = wb.Sheets[wb.SheetNames[0]];
  const rows = XLSX.utils.sheet_to_json<any[]>(ws, {
    header: 1,
    raw: true,
    defval: null,
    blankrows: false,
  });

  const cuentas: CuentaExcel[] = [];
  const descartadas: Descartada[] = [];
  const seen = new Set<string>();
  rows.forEach((r, idx) => {
    const codigoRaw = r[0];
    const descRaw = r[1];
    const codigoStr = codigoRaw != null ? String(codigoRaw).trim() : '';
    const descStr = descRaw != null ? String(descRaw).trim() : '';
    const fila = idx + 1;

    if (!codigoRaw && !descRaw) return; // fila totalmente vacía
    if (!codigoRaw) {
      descartadas.push({ codigo: '', descripcion: descStr, motivo: 'sin código', fila });
      return;
    }
    if (!descRaw) {
      descartadas.push({ codigo: codigoStr, descripcion: '', motivo: 'sin descripción', fila });
      return;
    }
    if (!/^[0-9]+(\.[0-9]+)*$/.test(codigoStr)) {
      descartadas.push({ codigo: codigoStr, descripcion: descStr, motivo: `formato de código inválido`, fila });
      return;
    }
    if (seen.has(codigoStr)) {
      descartadas.push({ codigo: codigoStr, descripcion: descStr, motivo: 'código duplicado en el Excel', fila });
      return;
    }
    seen.add(codigoStr);

    const raiz = codigoStr.split('.')[0];
    const tipo = TIPO_POR_RAIZ[raiz];
    if (!tipo) {
      descartadas.push({ codigo: codigoStr, descripcion: descStr, motivo: `raíz desconocida "${raiz}"`, fila });
      return;
    }
    cuentas.push({
      codigo: codigoStr,
      descripcion: descStr.slice(0, 200),
      nivel: codigoStr.split('.').length,
      tipo,
      naturaleza: naturalezaDe(tipo),
    });
  });

  cuentas.sort((a, b) => a.nivel - b.nivel || a.codigo.localeCompare(b.codigo));
  return { cuentas, descartadas };
}

function padreDe(codigo: string): string | null {
  const partes = codigo.split('.');
  if (partes.length <= 1) return null;
  return partes.slice(0, -1).join('.');
}

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

  try {
    const empresaRow = await prisma.empresas.findUnique({
      where: { id: empresa },
      select: { id: true, razon_social: true },
    });
    if (!empresaRow) throw new Error(`Empresa ${empresa} no existe`);
    console.log(`Empresa: ${empresaRow.razon_social} (${empresaRow.id})`);

    const { cuentas, descartadas } = leerExcel(excel);
    console.log(`Excel: ${cuentas.length} cuentas válidas, ${descartadas.length} descartadas`);
    const porNivel = cuentas.reduce<Record<number, number>>((acc, c) => {
      acc[c.nivel] = (acc[c.nivel] ?? 0) + 1;
      return acc;
    }, {});
    console.log('Por nivel:', porNivel);

    // Hijos por código
    const hijosPorCodigo = new Map<string, number>();
    for (const c of cuentas) {
      const p = padreDe(c.codigo);
      if (p) hijosPorCodigo.set(p, (hijosPorCodigo.get(p) ?? 0) + 1);
    }

    if (verify) {
      // Cuentas activas de la empresa (no sistema, no OLD-)
      const enDb = await prisma.cont_plan_cuentas.findMany({
        where: { empresa_id: empresa, is_sistema: false },
        select: { codigo: true, descripcion: true, active: true },
        orderBy: { codigo: 'asc' },
      });
      const dbActivasByCodigo = new Map(
        enDb.filter((c) => c.active && !c.codigo.startsWith('OLD-')).map((c) => [c.codigo, c]),
      );
      const excelByCodigo = new Map(cuentas.map((c) => [c.codigo, c]));

      const faltantes = cuentas.filter((c) => !dbActivasByCodigo.has(c.codigo));
      const extras = [...dbActivasByCodigo.values()].filter((c) => !excelByCodigo.has(c.codigo));

      console.log('\n═══ VERIFICACIÓN ═══');
      console.log(`  Excel válidas:              ${cuentas.length}`);
      console.log(`  BD activas (no sistema):    ${dbActivasByCodigo.size}`);
      console.log(`  Excel descartadas:          ${descartadas.length}`);
      console.log(`  Faltantes (Excel \\ BD):     ${faltantes.length}`);
      console.log(`  Extras   (BD \\ Excel):      ${extras.length}`);

      if (descartadas.length) {
        console.log('\n── Descartadas del Excel (no migradas por regla) ──');
        for (const d of descartadas) {
          console.log(`  fila ${d.fila}  [${d.codigo || '—'}]  ${d.descripcion || '—'}  · ${d.motivo}`);
        }
      }
      if (faltantes.length) {
        console.log('\n── En Excel pero NO en BD ──');
        for (const f of faltantes) console.log(`  ${f.codigo}  ${f.descripcion}`);
      }
      if (extras.length) {
        console.log('\n── En BD pero NO en Excel (posibles agregados manuales o del plan default) ──');
        for (const e of extras) console.log(`  ${e.codigo}  ${e.descripcion}`);
      }
      console.log('\n═══ FIN VERIFICACIÓN — nada se persistió ═══');
      await prisma.$disconnect();
      return;
    }

    const existentes = await prisma.cont_plan_cuentas.findMany({
      where: { empresa_id: empresa },
      select: { id: true, codigo: true, is_sistema: true },
    });
    const sistema = existentes.filter((e) => e.is_sistema).length;
    console.log(
      `Existentes a renombrar (OLD-): ${existentes.length} (sistema=${sistema}, usuario=${existentes.length - sistema})`,
    );

    if (dryRun) {
      console.log('--- DRY RUN: nada se persiste ---');
      console.log('Sample 10 cuentas a insertar:');
      console.log(cuentas.slice(0, 10));
      await prisma.$disconnect();
      return;
    }

    await prisma.$transaction(
      async (tx) => {
        // 1. Soft-replace de existentes
        for (const e of existentes) {
          const nuevoCodigo = `OLD-${e.codigo}`.slice(0, 20);
          await tx.cont_plan_cuentas.update({
            where: { id: e.id },
            data: { codigo: nuevoCodigo, active: false, is_sistema: false },
          });
        }

        // 2. Insertar por nivel ascendente para resolver padres
        const codigoToId = new Map<string, string>();
        for (const c of cuentas) {
          const padreCodigo = padreDe(c.codigo);
          const cuentaPadreId = padreCodigo ? codigoToId.get(padreCodigo) ?? null : null;
          if (padreCodigo && !cuentaPadreId) {
            console.warn(`⚠️  ${c.codigo}: padre ${padreCodigo} no encontrado, queda sin padre`);
          }
          // Raíces totalizadoras de resultado: no aceptan movimientos aunque sean hojas
          const esTotalizadorResultado =
            c.nivel === 1 && ['6', '9', '12', '16', '18', '20'].includes(c.codigo);
          const esHoja = !hijosPorCodigo.has(c.codigo) && !esTotalizadorResultado;
          const created = await tx.cont_plan_cuentas.create({
            data: {
              empresa_id: empresa,
              codigo: c.codigo,
              descripcion: c.descripcion,
              tipo: c.tipo,
              naturaleza: c.naturaleza,
              nivel: c.nivel,
              cuenta_padre_id: cuentaPadreId,
              acepta_movimientos: esHoja,
              acepta_cc: false,
              active: true,
              is_sistema: false,
            },
            select: { id: true },
          });
          codigoToId.set(c.codigo, created.id);
        }
      },
      { timeout: 120_000 },
    );

    const totalFinal = await prisma.cont_plan_cuentas.count({
      where: { empresa_id: empresa, active: true, is_sistema: false },
    });
    console.log(`✅ Listo. Cuentas activas de la empresa: ${totalFinal}`);
  } finally {
    await prisma.$disconnect();
  }
}

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