/**
 * ╔══════════════════════════════════════════════════════════════╗
 * ║   SCRIPT DE MIGRACIÓN: Productos + stock inicial desde xlsx   ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Lee un Excel exportado de un sistema legado (convertido      ║
 * ║  desde PDF) y crea/actualiza productos con su stock inicial   ║
 * ║  en el depósito principal de la sucursal 001 de la empresa.   ║
 * ║                                                                ║
 * ║  Columnas esperadas en la hoja "Productos":                   ║
 * ║    Item | Descripción | Costo real | Precio 0 | Existencia    ║
 * ║    total                                                       ║
 * ║                                                                ║
 * ║  Hace upsert de productos por (empresa_id, cod_producto).      ║
 * ║  El stock SOLO se carga cuando el producto se CREA en esta     ║
 * ║  corrida — si ya existía (ej. reejecución del script), no se   ║
 * ║  vuelve a tocar el stock. Así correrlo dos veces es seguro.    ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Instrucciones:                                                ║
 * ║  1. Dry-run (no escribe nada):                                 ║
 * ║     npx ts-node --transpile-only -r tsconfig-paths/register \  ║
 * ║       --project tsconfig.scripts.json \                        ║
 * ║       scripts/migrate-access/migrate-productos-agogo.ts \      ║
 * ║       --empresa=<uuid> --dry                                   ║
 * ║  2. Real (mismo comando sin --dry):                            ║
 * ║     ... migrate-productos-agogo.ts --empresa=<uuid>            ║
 * ║  3. Opcional, otro archivo:                                    ║
 * ║     ... --empresa=<uuid> --archivo=ruta/al/otro.xlsx            ║
 * ║                                                                ║
 * ║  --empresa es OBLIGATORIO. No hay empresa por defecto: correr  ║
 * ║  esto sin pensar contra la empresa equivocada crea productos   ║
 * ║  y stock reales que después hay que deshacer a mano.           ║
 * ╚══════════════════════════════════════════════════════════════╝
 */

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

// ══════════════════════════════════════════════════════════════════
//  CONFIG — catálogos globales (mismos para cualquier empresa; no
//  tienen empresa_id, así que no hace falta parametrizarlos)
// ══════════════════════════════════════════════════════════════════
const CATALOGO = {
  AFECTACION_ID: '7b634413-a746-4152-9941-4d586ed75825', // Gravado IVA (global)
  UNIDAD_MEDIDA_ID: '4f6a1318-1a42-454d-874e-a72f6cda04df', // Unidad (global)
  IVA_DEFAULT: 10,
  PROPORCION_IVA_DEFAULT: 100,
  TIPO_PRODUCTO: 'producto' as const,

  // Solo se carga stock en la sucursal con este punto de establecimiento,
  // en su depósito marcado como principal. Ver README del pedido original.
  PUNTO_ESTABLECIMIENTO: '001',

  DEFAULT_XLSX_PATH: path.resolve(__dirname, '../../scripts/data_script/cliente-agogo/Entradas09012026.xlsx'),
  HOJA: 'Productos',
};

// ══════════════════════════════════════════════════════════════════
//  ARGUMENTOS DE LÍNEA DE COMANDOS
// ══════════════════════════════════════════════════════════════════
function argValue(flag: string): string | undefined {
  const arg = process.argv.find((a) => a.startsWith(`--${flag}=`));
  return arg ? arg.split('=').slice(1).join('=') : undefined;
}

const DRY = process.argv.includes('--dry');
const archivoArg = argValue('archivo');
const XLSX_PATH = archivoArg ? path.resolve(process.cwd(), archivoArg) : CATALOGO.DEFAULT_XLSX_PATH;

const empresaArg = argValue('empresa');
if (!empresaArg) {
  console.error('✗✗FATAL Falta --empresa=<uuid>. Este script no corre contra una empresa por defecto.');
  console.error('        Ejemplo: ... migrate-productos-agogo.ts --empresa=7d3ce403-9487-4289-b21f-2e5d3477f5b5 --dry');
  process.exit(1);
}
const EMPRESA_ID: string = empresaArg;

// ══════════════════════════════════════════════════════════════════
//  LOGGER
// ══════════════════════════════════════════════════════════════════
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const LOG_FILE = path.join(__dirname, `reporte-productos-agogo-${timestamp}.log`);
const logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });

type LogLevel = 'INFO' | 'OK' | 'SKIP' | 'WARN' | 'ERROR' | 'FATAL' | 'RAW';

function log(level: LogLevel, msg: string) {
  const ts = new Date().toISOString().slice(11, 19);
  const prefix: Record<LogLevel, string> = {
    INFO: `[${ts}] INFO  `,
    OK: `[${ts}] ✓     `,
    SKIP: `[${ts}] ~     `,
    WARN: `[${ts}] WARN  `,
    ERROR: `[${ts}] ✗ ERR `,
    FATAL: `[${ts}] ✗✗FATAL`,
    RAW: '',
  };
  const line = prefix[level] + msg;
  console.log(line);
  logStream.write(line + '\n');
}

const info = (m: string) => log('INFO', m);
const ok = (m: string) => log('OK', m);
const skip = (m: string) => log('SKIP', m);
const warn = (m: string) => log('WARN', m);
const err = (m: string) => log('ERROR', m);
const raw = (m: string) => log('RAW', m);

// ══════════════════════════════════════════════════════════════════
//  HELPERS
// ══════════════════════════════════════════════════════════════════
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;
  const s = String(val).replace(/\./g, '').replace(',', '.');
  const n = parseFloat(s);
  return isNaN(n) ? 0 : n;
}

// Guaraní no usa decimales
function toPrice(val: any): number {
  return Math.max(0, Math.round(toNum(val)));
}

// ══════════════════════════════════════════════════════════════════
//  MAIN
// ══════════════════════════════════════════════════════════════════
const prisma = new PrismaClient();

async function main() {
  raw('');
  raw('╔══════════════════════════════════════════════════╗');
  raw(`║  MIGRACIÓN PRODUCTOS + STOCK  ${DRY ? '[ DRY RUN ]  ' : '[ REAL — ESCRIBE ]'} ║`);
  raw(`║  Empresa: ${EMPRESA_ID.padEnd(39)}║`);
  raw(`║  Archivo: ${path.basename(XLSX_PATH).padEnd(39)}║`);
  raw(`║  Log:     ${path.basename(LOG_FILE).padEnd(39)}║`);
  raw('╚══════════════════════════════════════════════════╝');
  raw('');

  // ── Resolver empresa, sucursal 001 y depósito principal ─────────
  const empresa = await prisma.empresas.findUnique({
    where: { id: EMPRESA_ID },
    select: { id: true, razon_social: true },
  });
  if (!empresa) {
    log('FATAL', `No existe ninguna empresa con id ${EMPRESA_ID}`);
    process.exit(1);
  }
  info(`Empresa: ${empresa.razon_social} (${empresa.id})`);

  const sucursal = await prisma.empresas_sucursales.findFirst({
    where: { empresa_id: EMPRESA_ID, punto_establecimiento: CATALOGO.PUNTO_ESTABLECIMIENTO },
    select: { id: true, descripcion: true },
  });
  if (!sucursal) {
    log(
      'FATAL',
      `La empresa no tiene ninguna sucursal con punto_establecimiento='${CATALOGO.PUNTO_ESTABLECIMIENTO}'. Creala antes de correr este script.`,
    );
    process.exit(1);
  }
  info(`Sucursal ${CATALOGO.PUNTO_ESTABLECIMIENTO}: ${sucursal.descripcion} (${sucursal.id})`);

  const deposito = await prisma.depositos.findFirst({
    where: { sucursal_id: sucursal.id, es_principal: true },
    select: { id: true, descripcion: true },
  });
  if (!deposito) {
    log(
      'FATAL',
      `La sucursal ${CATALOGO.PUNTO_ESTABLECIMIENTO} no tiene ningún depósito marcado como principal (es_principal=true). Marcá uno antes de correr este script.`,
    );
    process.exit(1);
  }
  info(`Depósito principal: ${deposito.descripcion} (${deposito.id})`);

  const tipoEntrada = await prisma.tipo_movimiento_inventario.findUnique({
    where: { codigo: 'ENTRADA' },
    select: { id: true },
  });
  if (!tipoEntrada) {
    log('FATAL', `No existe el tipo de movimiento 'ENTRADA' en tipo_movimiento_inventario.`);
    process.exit(1);
  }
  raw('');

  // ── Leer Excel ────────────────────────────────────────────────
  if (!fs.existsSync(XLSX_PATH)) {
    log('FATAL', `Archivo no encontrado: ${XLSX_PATH}`);
    process.exit(1);
  }

  const wb = XLSX.readFile(XLSX_PATH);
  if (!wb.SheetNames.includes(CATALOGO.HOJA)) {
    log(
      'FATAL',
      `El archivo no tiene una hoja llamada "${CATALOGO.HOJA}". Hojas encontradas: ${wb.SheetNames.join(', ')}`,
    );
    process.exit(1);
  }
  const ws = wb.Sheets[CATALOGO.HOJA];
  const rows: any[] = XLSX.utils.sheet_to_json(ws, { defval: null });

  info(`Filas leídas del Excel : ${rows.length}`);
  info(`Columnas detectadas    : ${Object.keys(rows[0] ?? {}).join(', ')}`);
  raw('');

  // ── Contadores ────────────────────────────────────────────────
  let creados = 0;
  let actualizados = 0;
  let omitidosSinDatos = 0;
  let conStockCargado = 0;
  let existenciaNegativaOmitida = 0;
  const erroresList: string[] = [];
  const existenciaNegativaLog: string[] = [];

  // ── Procesar filas ────────────────────────────────────────────
  for (const r of rows) {
    const codProducto = toStr(r['Item']);
    const descripcion = toStr(r['Descripción']);
    const precioCosto = toPrice(r['Costo real']);
    const precio = toPrice(r['Precio 0']);
    const existenciaTotal = toNum(r['Existencia total']);

    // Basura de la conversión PDF→Excel: filas sin código o sin
    // descripción no representan un producto real (ver hoja
    // "Información" del propio archivo).
    if (!codProducto || !descripcion) {
      warn(`Fila omitida — sin código o descripción: ${JSON.stringify(r)}`);
      omitidosSinDatos++;
      continue;
    }

    const label = `[${codProducto}] ${descripcion}`;

    if (DRY) {
      const notaStock =
        existenciaTotal > 0
          ? `stock ${existenciaTotal}`
          : existenciaTotal < 0
            ? `existencia negativa (${existenciaTotal}) — NO se cargaría stock`
            : 'sin stock';
      info(`[DRY] ${label} | costo: ${precioCosto} | precio: ${precio} | ${notaStock}`);
      creados++;
      if (existenciaTotal > 0) conStockCargado++;
      if (existenciaTotal < 0) existenciaNegativaOmitida++;
      continue;
    }

    try {
      const existing = await prisma.productos.findFirst({
        where: { empresa_id: EMPRESA_ID, cod_producto: codProducto },
        select: { id: true },
      });

      const data = {
        descripcion,
        cod_producto: codProducto,
        precio_costo: precioCosto,
        precio,
        afectacion_id: CATALOGO.AFECTACION_ID,
        unidad_medida_id: CATALOGO.UNIDAD_MEDIDA_ID,
        porcentaje_iva: CATALOGO.IVA_DEFAULT,
        proporcion_iva: CATALOGO.PROPORCION_IVA_DEFAULT,
        tipo: CATALOGO.TIPO_PRODUCTO,
        maneja_inventario: true,
        maneja_lote: false,
      };

      let productoId: string;
      let esNuevo: boolean;

      if (existing) {
        await prisma.productos.update({ where: { id: existing.id }, data });
        productoId = existing.id;
        esNuevo = false;
        skip(`Actualizado (sin tocar stock) | ${label}`);
        actualizados++;
      } else {
        const creado = await prisma.productos.create({
          data: { ...data, empresa_id: EMPRESA_ID },
          select: { id: true },
        });
        productoId = creado.id;
        esNuevo = true;
        ok(`Creado | ${label}`);
        creados++;
      }

      // El stock inicial solo se carga la primera vez que el
      // producto se crea. Si ya existía (reejecución del script),
      // no se vuelve a sumar — evita duplicar cantidades.
      if (esNuevo && existenciaTotal > 0) {
        await prisma.$transaction([
          prisma.stock_deposito.upsert({
            where: { deposito_id_producto_id: { deposito_id: deposito.id, producto_id: productoId } },
            create: { deposito_id: deposito.id, producto_id: productoId, cantidad_disponible: existenciaTotal },
            update: { cantidad_disponible: { increment: existenciaTotal } },
          }),
          prisma.movimientos_inventario.create({
            data: {
              deposito_origen_id: deposito.id,
              producto_id: productoId,
              tipo_movimiento_id: tipoEntrada.id,
              cantidad: existenciaTotal,
              documento_origen: 'migracion_inicial',
              observaciones: `Carga inicial de stock — migración desde ${path.basename(XLSX_PATH)}`,
            },
          }),
        ]);
        ok(`  └─ stock inicial ${existenciaTotal} en ${deposito.descripcion}`);
        conStockCargado++;
      } else if (esNuevo && existenciaTotal < 0) {
        // Replica la misma regla que POST /stock/ajustar: nunca se
        // deja stock negativo. El producto se crea, el stock queda
        // en 0 y se lista para revisión manual.
        warn(`  └─ existencia negativa en el archivo (${existenciaTotal}) — NO se cargó stock, queda en 0`);
        existenciaNegativaOmitida++;
        existenciaNegativaLog.push(`${label} | existencia en archivo: ${existenciaTotal}`);
      }
    } catch (e: any) {
      const msg = `${label} | ${e.message}`;
      erroresList.push(msg);
      err(msg);
    }
  }

  // ── Resumen ───────────────────────────────────────────────────
  raw('');
  raw('╔══════════════════════════════════════════════════╗');
  raw('║                  RESUMEN FINAL                   ║');
  raw('╠══════════════════════════════════════════════════╣');
  raw(`║  Total filas Excel         : ${String(rows.length).padStart(6)}             ║`);
  raw(`║  Creados                   : ${String(creados).padStart(6)}             ║`);
  raw(`║  Actualizados              : ${String(actualizados).padStart(6)}             ║`);
  raw(`║  Con stock inicial cargado : ${String(conStockCargado).padStart(6)}             ║`);
  raw(`║  Existencia negativa (sin  :                     ║`);
  raw(`║    cargar stock)           : ${String(existenciaNegativaOmitida).padStart(6)}             ║`);
  raw(`║  Omitidos (sin código/desc): ${String(omitidosSinDatos).padStart(6)}             ║`);
  raw(`║  Errores                   : ${String(erroresList.length).padStart(6)}             ║`);
  raw('╚══════════════════════════════════════════════════╝');

  if (existenciaNegativaLog.length > 0) {
    raw('');
    raw('── PRODUCTOS CON EXISTENCIA NEGATIVA EN EL ARCHIVO (revisar a mano) ──');
    existenciaNegativaLog.forEach((e, i) => raw(`  [${String(i + 1).padStart(4)}] ${e}`));
  }

  if (erroresList.length > 0) {
    raw('');
    raw('── ERRORES ──────────────────────────────────────');
    erroresList.forEach((e, i) => raw(`  [${String(i + 1).padStart(3)}] ${e}`));
  }

  raw('');
  if (DRY) {
    raw('⚠  DRY RUN — No se escribió nada en la base de datos.');
    raw('   Ejecutar sin --dry para aplicar la migración real.');
  } else {
    raw(
      erroresList.length === 0
        ? '✓  Migración completada sin errores.'
        : `⚠  Migración completada con ${erroresList.length} error(es). Revisar sección ERRORES arriba.`,
    );
  }
  raw(`\nLog guardado en: ${LOG_FILE}\n`);
}

main()
  .catch((e) => {
    log('FATAL', `Error fatal: ${e.message}\n${e.stack}`);
    process.exit(1);
  })
  .finally(async () => {
    logStream.end();
    await prisma.$disconnect();
  });
