/**
 * ╔══════════════════════════════════════════════════════════════╗
 * ║        SCRIPT DE MIGRACIÓN: Artículos Access → ERP          ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Crea / actualiza:                                           ║
 * ║    • marcas       (por descripcion)                          ║
 * ║    • categorias   (familia como padre, subfamilia como hijo) ║
 * ║    • productos    (upsert por cod_producto)                  ║
 * ║    • depositos    (desde sucursales.xlsx)                    ║
 * ║    • stock_deposito (desde stocks.xlsx)                      ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Instrucciones:                                              ║
 * ║  1. Colocar articulos.xlsx, familias.xlsx, marcas.xlsx,      ║
 * ║     sub_familias.xlsx, sucursales.xlsx y stocks.xlsx         ║
 * ║     en data_exportar/                                        ║
 * ║  2. Dry-run:                                                 ║
 * ║     npx ts-node scripts/migrate-access/migrate-articulos.ts --dry ║
 * ║  3. Real:                                                    ║
 * ║     npx ts-node scripts/migrate-access/migrate-articulos.ts  ║
 * ╚══════════════════════════════════════════════════════════════╝
 */

import * as path from 'path';
import * as XLSX from 'xlsx';
import { PrismaClient } from '@prisma/client';

// ══════════════════════════════════════════════════════════════════
//  CONFIG
// ══════════════════════════════════════════════════════════════════
const CONFIG = {
  EMPRESA_ID: '3b327470-82de-4c8b-bc6f-070f38a4f238',
  SUCURSAL_ID: '5d0291c4-54d3-49df-8900-c732cb9fc2e1', // sucursal principal (punto 001)

  // Campos fijos de productos
  AFECTACION_ID: '7b634413-a746-4152-9941-4d586ed75825',
  UNIDAD_MEDIDA_ID: '4f6a1318-1a42-454d-874e-a72f6cda04df',
  PORCENTAJE_IVA: 10,
  PROPORCION_IVA: 10,
  TIPO: 'producto',
  MANEJA_INVENTARIO: true,
};

const DATA_DIR = path.resolve(__dirname, '../../data_exportar');
const DRY = process.argv.includes('--dry');
const prisma = new PrismaClient();

// ══════════════════════════════════════════════════════════════════
//  HELPERS
// ══════════════════════════════════════════════════════════════════

function readSheet(filename: string): any[] {
  const filepath = path.join(DATA_DIR, filename);
  const workbook = XLSX.readFile(filepath);
  const sheet = workbook.Sheets[workbook.SheetNames[0]];
  return XLSX.utils.sheet_to_json(sheet, { defval: null });
}

function toStr(val: any): string {
  if (val === null || val === undefined) return '';
  return String(val).trim();
}

function toNum(val: any): number {
  if (val === null || val === undefined) return 0;
  // Handle comma as thousands separator or decimal
  const s = String(val).replace(/\./g, '').replace(',', '.');
  const n = parseFloat(s);
  return isNaN(n) ? 0 : n;
}

// Guaraní no usa decimales → redondeamos a entero
// Columna: DECIMAL(19,4) tras migración 20260419_fix_precio_precision
function toPrice(val: any): number {
  const n = Math.round(toNum(val));
  return n < 0 ? 0 : n;
}

function slugify(str: string): string {
  return str
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '');
}

// ══════════════════════════════════════════════════════════════════
//  MAIN
// ══════════════════════════════════════════════════════════════════

async function main() {
  console.log(`\n${'═'.repeat(60)}`);
  console.log(`  MIGRACIÓN DE ARTÍCULOS  ${DRY ? '[DRY-RUN — no se guarda nada]' : '[MODO REAL]'}`);
  console.log(`${'═'.repeat(60)}\n`);

  // ── 1. Leer Excel ──────────────────────────────────────────────
  console.log('📂 Leyendo archivos Excel...');
  const rowsArticulos = readSheet('articulos.xlsx');
  const rowsFamilias = readSheet('familias.xlsx');
  const rowsMarcas = readSheet('marcas.xlsx');
  const rowsSubFamilias = readSheet('sub_familias.xlsx');
  const rowsSucursales = readSheet('sucursales.xlsx');
  const rowsStocks = readSheet('stocks.xlsx');

  console.log(`   articulos.xlsx    : ${rowsArticulos.length} filas`);
  console.log(`   familias.xlsx     : ${rowsFamilias.length} filas`);
  console.log(`   marcas.xlsx       : ${rowsMarcas.length} filas`);
  console.log(`   sub_familias.xlsx : ${rowsSubFamilias.length} filas`);
  console.log(`   sucursales.xlsx   : ${rowsSucursales.length} filas`);
  console.log(`   stocks.xlsx       : ${rowsStocks.length} filas\n`);

  // ── 2. Construir mapas de referencia de Excel ──────────────────
  // familias: { id_access → descripcion }
  const familiaMap = new Map<number, string>();
  for (const r of rowsFamilias) {
    const id = Number(r['familia'] ?? r['id'] ?? r['ID']);
    const desc = toStr(r['descripcion'] ?? r['Descripcion'] ?? r['DESCRIPCION']);
    if (id && desc) familiaMap.set(id, desc);
  }

  // subfamilias: { id_access → { descripcion, familia_id } }
  const subFamiliaMap = new Map<number, { descripcion: string; familiaId: number }>();
  for (const r of rowsSubFamilias) {
    const id = Number(r['sub_familia'] ?? r['id'] ?? r['ID']);
    const desc = toStr(r['descripcion'] ?? r['Descripcion'] ?? r['DESCRIPCION']);
    const famId = Number(r['familia'] ?? r['familia_id'] ?? 0);
    if (id && desc) subFamiliaMap.set(id, { descripcion: desc, familiaId: famId });
  }

  // marcas: { id_access → descripcion }
  const marcaExcelMap = new Map<number, string>();
  for (const r of rowsMarcas) {
    const id = Number(r['marca'] ?? r['id'] ?? r['ID']);
    const desc = toStr(r['descripcion'] ?? r['Descripcion'] ?? r['DESCRIPCION']);
    if (id && desc) marcaExcelMap.set(id, desc);
  }

  // ── 3. Upsert MARCAS ───────────────────────────────────────────
  console.log('🏷️  Procesando marcas...');

  // Collect unique marca descriptions from articulos via marcaExcelMap (r['marca'] es ID numérico)
  const marcasEnArticulos = new Set<string>();
  for (const r of rowsArticulos) {
    const desc = marcaExcelMap.get(Number(r['marca']));
    if (desc) marcasEnArticulos.add(desc.toUpperCase());
  }
  // También incluir marcas del xlsx que no aparezcan en artículos
  for (const desc of marcaExcelMap.values()) {
    marcasEnArticulos.add(desc.toUpperCase());
  }

  // marca_id by descripcion uppercase
  const marcaIdByDesc = new Map<string, string>();

  if (!DRY) {
    for (const desc of marcasEnArticulos) {
      const existing = await prisma.marcas.findFirst({
        where: {
          empresa_id: CONFIG.EMPRESA_ID,
          descripcion: { equals: desc, mode: 'insensitive' },
        },
        select: { id: true },
      });

      if (existing) {
        marcaIdByDesc.set(desc, existing.id);
      } else {
        const created = await prisma.marcas.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            descripcion: desc,
            codigo: slugify(desc).substring(0, 20),
            active: true,
          },
          select: { id: true },
        });
        marcaIdByDesc.set(desc, created.id);
      }
    }
    console.log(`   ✓ ${marcaIdByDesc.size} marcas procesadas\n`);
  } else {
    console.log(`   [DRY] ${marcasEnArticulos.size} marcas a procesar\n`);
  }

  // ── 4. Upsert CATEGORIAS (familias como padre) ─────────────────
  console.log('📁 Procesando familias (categorías padre)...');

  // Collect unique familias from articulos — r['familia'] es ID numérico, convertir via familiaMap
  const familiasEnArticulos = new Set<string>();
  for (const r of rowsArticulos) {
    const desc = familiaMap.get(Number(r['familia']));
    if (desc) familiasEnArticulos.add(desc);
  }

  // categoria_id by familia descripcion
  const categoriaIdByFamilia = new Map<string, string>();

  if (!DRY) {
    for (const desc of familiasEnArticulos) {
      const existing = await prisma.categorias.findFirst({
        where: {
          empresa_id: CONFIG.EMPRESA_ID,
          descripcion: { equals: desc, mode: 'insensitive' },
          padre_id: null,
        },
        select: { id: true },
      });

      if (existing) {
        categoriaIdByFamilia.set(desc, existing.id);
      } else {
        const created = await prisma.categorias.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            descripcion: desc,
            activo: true,
          },
          select: { id: true },
        });
        categoriaIdByFamilia.set(desc, created.id);
      }
    }
    console.log(`   ✓ ${categoriaIdByFamilia.size} familias procesadas\n`);
  } else {
    console.log(`   [DRY] ${familiasEnArticulos.size} familias a procesar\n`);
  }

  // ── 5. Upsert CATEGORIAS (subfamilias como hijo) ───────────────
  console.log('📂 Procesando subfamilias (categorías hijo)...');

  // Collect unique subfamilia+familia combos — ambos son IDs numéricos, convertir via mapas
  const subfamiliasEnArticulos = new Map<string, string>(); // subfamDesc → familiaDesc
  for (const r of rowsArticulos) {
    const subfamDesc = subFamiliaMap.get(Number(r['subfamilia']))?.descripcion;
    const famDesc = familiaMap.get(Number(r['familia']));
    if (subfamDesc && famDesc) subfamiliasEnArticulos.set(subfamDesc, famDesc);
  }

  // categoria_id by subfamilia descripcion
  const categoriaIdBySubfamilia = new Map<string, string>();

  if (!DRY) {
    for (const [subfamDesc, famDesc] of subfamiliasEnArticulos) {
      const padreId = categoriaIdByFamilia.get(famDesc) ?? null;

      const existing = await prisma.categorias.findFirst({
        where: {
          empresa_id: CONFIG.EMPRESA_ID,
          descripcion: { equals: subfamDesc, mode: 'insensitive' },
          padre_id: padreId,
        },
        select: { id: true },
      });

      if (existing) {
        categoriaIdBySubfamilia.set(subfamDesc, existing.id);
      } else {
        const created = await prisma.categorias.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            descripcion: subfamDesc,
            padre_id: padreId,
            activo: true,
          },
          select: { id: true },
        });
        categoriaIdBySubfamilia.set(subfamDesc, created.id);
      }
    }
    console.log(`   ✓ ${categoriaIdBySubfamilia.size} subfamilias procesadas\n`);
  } else {
    console.log(`   [DRY] ${subfamiliasEnArticulos.size} subfamilias a procesar\n`);
  }

  // ── 6. Upsert PRODUCTOS ────────────────────────────────────────
  console.log('📦 Procesando productos...');

  let creados = 0;
  let actualizados = 0;
  let omitidos = 0;
  const errores: string[] = [];

  // mapa articuloAccessId → producto UUID (para stock)
  const articuloIdToProductoId = new Map<number, string>();

  for (const r of rowsArticulos) {
    const codProducto = toStr(r['codigo_referencial']);
    const descripcion = toStr(r['descripcion']);

    if (!descripcion) {
      omitidos++;
      continue;
    }

    const marcaDesc = (marcaExcelMap.get(Number(r['marca'])) ?? '').toUpperCase();
    const familiaDesc = familiaMap.get(Number(r['familia'])) ?? '';
    const subfamiliaDesc = subFamiliaMap.get(Number(r['subfamilia']))?.descripcion ?? '';

    const marcaId = marcaDesc ? (marcaIdByDesc.get(marcaDesc) ?? null) : null;
    // Prefer subfamilia category (more specific), fallback to familia
    const categoriaId =
      (subfamiliaDesc ? categoriaIdBySubfamilia.get(subfamiliaDesc) : undefined) ??
      (familiaDesc ? categoriaIdByFamilia.get(familiaDesc) : undefined) ??
      null;

    const precioCosto = toPrice(r['costo']);
    const precio = toPrice(r['venta']);
    const articuloAccessId = Number(r['articulo']);

    if (DRY) {
      // Just count
      creados++;
      continue;
    }

    try {
      let productoId: string | null = null;

      if (codProducto) {
        // Upsert by cod_producto
        const existing = await prisma.productos.findFirst({
          where: {
            empresa_id: CONFIG.EMPRESA_ID,
            OR: [{ cod_producto: codProducto }, { descripcion: { equals: descripcion, mode: 'insensitive' } }],
          },
          select: { id: true },
        });

        const data = {
          descripcion,
          marca_id: marcaId,
          categoria_id: categoriaId,
          precio_costo: precioCosto,
          precio,
          afectacion_id: CONFIG.AFECTACION_ID,
          unidad_medida_id: CONFIG.UNIDAD_MEDIDA_ID,
          porcentaje_iva: CONFIG.PORCENTAJE_IVA,
          proporcion_iva: CONFIG.PROPORCION_IVA,
          tipo: CONFIG.TIPO,
          maneja_inventario: CONFIG.MANEJA_INVENTARIO,
        };

        if (existing) {
          await prisma.productos.update({ where: { id: existing.id }, data });
          productoId = existing.id;
          actualizados++;
        } else {
          const created = await prisma.productos.create({
            data: { ...data, empresa_id: CONFIG.EMPRESA_ID, cod_producto: codProducto },
            select: { id: true },
          });
          productoId = created.id;
          creados++;
        }
      } else {
        // No code — create by descripcion (check if exists first)
        const existing = await prisma.productos.findFirst({
          where: {
            empresa_id: CONFIG.EMPRESA_ID,
            descripcion: { equals: descripcion, mode: 'insensitive' },
          },
          select: { id: true },
        });

        const data = {
          descripcion,
          marca_id: marcaId,
          categoria_id: categoriaId,
          precio_costo: precioCosto,
          precio,
          afectacion_id: CONFIG.AFECTACION_ID,
          unidad_medida_id: CONFIG.UNIDAD_MEDIDA_ID,
          porcentaje_iva: CONFIG.PORCENTAJE_IVA,
          proporcion_iva: CONFIG.PROPORCION_IVA,
          tipo: CONFIG.TIPO,
          maneja_inventario: CONFIG.MANEJA_INVENTARIO,
        };

        if (existing) {
          await prisma.productos.update({ where: { id: existing.id }, data });
          productoId = existing.id;
          actualizados++;
        } else {
          const created = await prisma.productos.create({
            data: { ...data, empresa_id: CONFIG.EMPRESA_ID },
            select: { id: true },
          });
          productoId = created.id;
          creados++;
        }
      }

      if (productoId && articuloAccessId) {
        articuloIdToProductoId.set(articuloAccessId, productoId);
      }
    } catch (e: any) {
      errores.push(`[${codProducto || descripcion}] ${e.message}`);
    }
  }

  // ── 7. Upsert DEPOSITOS (desde sucursales.xlsx) ────────────────
  console.log('🏪 Procesando sucursales → depósitos...');

  // sucursalAccessId → deposito UUID
  const sucursalIdToDepositoId = new Map<number, string>();

  if (!DRY) {
    for (const r of rowsSucursales) {
      const sucursalAccessId = Number(r['sucursal']);
      const descripcion = toStr(r['descripcion']);
      if (!descripcion || !sucursalAccessId) continue;

      const codigo = slugify(descripcion).substring(0, 20) || `DEP${sucursalAccessId}`;

      const existing = await prisma.depositos.findFirst({
        where: {
          empresa_id: CONFIG.EMPRESA_ID,
          descripcion: { equals: descripcion, mode: 'insensitive' },
        },
        select: { id: true },
      });

      if (existing) {
        sucursalIdToDepositoId.set(sucursalAccessId, existing.id);
      } else {
        const created = await prisma.depositos.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            sucursal_id: CONFIG.SUCURSAL_ID,
            descripcion,
            codigo,
            active: true,
          },
          select: { id: true },
        });
        sucursalIdToDepositoId.set(sucursalAccessId, created.id);
      }
    }
    console.log(`   ✓ ${sucursalIdToDepositoId.size} depósitos procesados\n`);
  } else {
    console.log(`   [DRY] ${rowsSucursales.length} sucursales a procesar como depósitos\n`);
  }

  // ── 8. Upsert STOCK_DEPOSITO (desde stocks.xlsx) ───────────────
  console.log('📊 Procesando stocks...');

  let stockCreados = 0;
  let stockSinProducto = 0;
  let stockSinDeposito = 0;
  let stockSinSucursal = 0;
  const stockErrores: string[] = [];

  for (const r of rowsStocks) {
    const articuloAccessId = Number(r['articulo']);
    const sucursalAccessId = r['sucursal'] != null ? Number(r['sucursal']) : null;
    const existencia = r['existencia'] != null ? Number(r['existencia']) : 0;

    if (!sucursalAccessId) {
      stockSinSucursal++;
      continue;
    }

    if (DRY) {
      stockCreados++;
      continue;
    }

    const productoId = articuloIdToProductoId.get(articuloAccessId);
    if (!productoId) {
      stockSinProducto++;
      continue;
    }

    const depositoId = sucursalIdToDepositoId.get(sucursalAccessId);
    if (!depositoId) {
      stockSinDeposito++;
      continue;
    }

    try {
      await prisma.stock_deposito.upsert({
        where: {
          deposito_id_producto_id: { deposito_id: depositoId, producto_id: productoId },
        },
        update: { cantidad_disponible: existencia, updated_at: new Date() },
        create: {
          deposito_id: depositoId,
          producto_id: productoId,
          cantidad_disponible: existencia,
        },
      });
      stockCreados++;
    } catch (e: any) {
      stockErrores.push(`[art:${articuloAccessId} suc:${sucursalAccessId}] ${e.message}`);
    }
  }

  if (!DRY) {
    console.log(`   ✓ ${stockCreados} registros de stock procesados`);
    if (stockSinSucursal > 0) console.log(`   ⚠  ${stockSinSucursal} sin sucursal (ignorados)`);
    if (stockSinProducto > 0) console.log(`   ⚠  ${stockSinProducto} artículos no encontrados en ERP`);
    if (stockSinDeposito > 0) console.log(`   ⚠  ${stockSinDeposito} sucursales no encontradas en ERP`);
    if (stockErrores.length > 0) {
      console.log(`   ✗  ${stockErrores.length} errores:`);
      for (const e of stockErrores.slice(0, 10)) console.log(`      ${e}`);
    }
    console.log();
  } else {
    console.log(`   [DRY] ${rowsStocks.length} registros de stock a procesar\n`);
  }

  // ── 9. Reporte final ───────────────────────────────────────────
  console.log(`\n${'═'.repeat(60)}`);
  console.log(`  RESULTADO ${DRY ? '[DRY-RUN]' : '[REAL]'}`);
  console.log(`${'═'.repeat(60)}`);
  if (DRY) {
    console.log(`  Productos a importar : ${creados}`);
    console.log(`  Omitidos (sin desc.) : ${omitidos}`);
    console.log(`  Depósitos a crear    : ${rowsSucursales.length}`);
    console.log(`  Stocks a procesar    : ${rowsStocks.length}`);
  } else {
    console.log(`  Productos creados    : ${creados}`);
    console.log(`  Productos actualizados: ${actualizados}`);
    console.log(`  Omitidos (sin desc.) : ${omitidos}`);
    console.log(`  Depósitos procesados : ${sucursalIdToDepositoId.size}`);
    console.log(`  Stock procesados     : ${stockCreados}`);
    console.log(`  Errores productos    : ${errores.length}`);
    console.log(`  Errores stock        : ${stockErrores.length}`);
    if (errores.length > 0) {
      console.log('\n  Detalle errores productos:');
      for (const e of errores.slice(0, 20)) {
        console.log(`    ✗ ${e}`);
      }
      if (errores.length > 20) console.log(`    ... y ${errores.length - 20} más`);
    }
    if (stockErrores.length > 0) {
      console.log('\n  Detalle errores stock:');
      for (const e of stockErrores.slice(0, 10)) {
        console.log(`    ✗ ${e}`);
      }
    }
  }
  console.log(`${'═'.repeat(60)}\n`);
}

main()
  .catch((e) => {
    console.error('Error fatal:', e);
    process.exit(1);
  })
  .finally(() => prisma.$disconnect());
