/**
 * ╔══════════════════════════════════════════════════════════════════════════╗
 * ║  IMPORTACIÓN: Proveedores (2).xlsx → personas + proveedores             ║
 * ╠══════════════════════════════════════════════════════════════════════════╣
 * ║  Uso:                                                                    ║
 * ║    npx ts-node scripts/data_script/cliente-naviera/import-proveedores-naviera.ts \
 * ║      --ruc=80100092 --dry                                                ║
 * ║    npx ts-node scripts/data_script/cliente-naviera/import-proveedores-naviera.ts \
 * ║      --ruc=80100092                                                      ║
 * ║                                                                          ║
 * ║  Flags:                                                                  ║
 * ║    --ruc=<8 dígitos>   RUC sin DV de la empresa destino   (obligatorio)  ║
 * ║    --file=<ruta>       Excel (default: "Proveedores (2).xlsx" de acá)    ║
 * ║    --hoja=<nombre>     Hoja a leer (default: la primera)                 ║
 * ║    --tipo-entidad=<X>  PROVEEDOR_LOCAL (default) | ACREEDOR_VARIO |      ║
 * ║                        PROVEEDOR_EXTERIOR                                ║
 * ║    --dry               Simula: no escribe nada en la BD                  ║
 * ╠══════════════════════════════════════════════════════════════════════════╣
 * ║  Columnas (encabezado en la 2ª fila, se buscan por nombre):              ║
 * ║    Fecha | C.I. / RUC | Nombre | Número del Timbrado                     ║
 * ║                                                                          ║
 * ║  OJO: la planilla NO es un padrón de proveedores sino un listado de      ║
 * ║  comprobantes de compra del ejercicio, así que el mismo RUC aparece      ║
 * ║  varias veces con distinto timbrado. Se deduplica por RUC quedándose     ║
 * ║  con la fila de FECHA MÁS RECIENTE, que trae el timbrado vigente.        ║
 * ║  `Fecha` es la fecha del comprobante, NO el vencimiento del timbrado:    ║
 * ║  por eso `fecha_vencimiento_timbrado` queda en null.                     ║
 * ║                                                                          ║
 * ║  Todos son contribuyentes (tienen RUC-DV válido y timbrado propio).      ║
 * ║  El tipo sale del número: > 80.000.000 → jurídica, si no → física.       ║
 * ╚══════════════════════════════════════════════════════════════════════════╝
 */

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

// ══════════════════════════════════════════════════════════════════════════
//  ARGUMENTOS
// ══════════════════════════════════════════════════════════════════════════
const args = process.argv.slice(2);
const flag = (nombre: string): string | undefined => {
  const hit = args.find((a) => a.startsWith(`--${nombre}=`));
  return hit ? hit.slice(nombre.length + 3) : undefined;
};

const RUC_EMPRESA = flag('ruc');
const HOJA = flag('hoja');
const DRY = args.includes('--dry');
const XLSX_FILE = path.resolve(flag('file') ?? path.join(__dirname, 'Proveedores (2).xlsx'));

const TIPOS_ENTIDAD = ['PROVEEDOR_LOCAL', 'PROVEEDOR_EXTERIOR', 'ACREEDOR_VARIO'] as const;
const TIPO_ENTIDAD = flag('tipo-entidad') ?? 'PROVEEDOR_LOCAL';

// ══════════════════════════════════════════════════════════════════════════
//  CATÁLOGOS SIFEN
// ══════════════════════════════════════════════════════════════════════════
const CAT = {
  NATURALEZA_CONTRIBUYENTE: '45f32973-7d88-4d47-b2ea-d63b4d448d54', // cód 1
  TIPO_CONTRIBUYENTE_FISICA: 'da9fdf2b-d72c-4bb7-8689-526a7f846c83', // cód 1
  TIPO_CONTRIBUYENTE_JURIDICA: 'b0da490e-ff65-4c8f-a5e5-823e17a67e9e', // cód 2
  PAIS_PARAGUAY: '253381af-b7e6-4c2f-9e90-33be1aea687b', // PRY
};

// ══════════════════════════════════════════════════════════════════════════
//  LOG
// ══════════════════════════════════════════════════════════════════════════
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const LOG_FILE = path.join(__dirname, `import-proveedores-naviera-${stamp}.log`);
const logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });
const say = (linea: string) => {
  console.log(linea);
  logStream.write(linea + '\n');
};

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

/** Lee una columna por nombre, tolerando mayúsculas, espacios dobles y acentos. */
function col(row: Record<string, any>, ...alias: string[]): string {
  const norm = (s: string) =>
    s.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/\s+/g, ' ').trim();
  for (const a of alias) {
    const objetivo = norm(a);
    const k = Object.keys(row).find((key) => norm(key) === objetivo);
    if (k !== undefined) return String(row[k] ?? '').trim();
  }
  return '';
}

/** Serial de fecha de Excel → Date (epoch 1899-12-30, el que usa LibreOffice/Excel). */
function fechaExcel(valor: any): Date | null {
  const n = Number(valor);
  if (!Number.isFinite(n) || n <= 0) return null;
  return new Date(Date.UTC(1899, 11, 30) + n * 86_400_000);
}

/** Dígito verificador módulo 11 (mismo cálculo que usa la SET). */
function calcularDV(numero: string, baseMax = 11): number {
  let total = 0;
  let k = 2;
  let alfanumerico = '';
  for (const c of numero) alfanumerico += /\d/.test(c) ? c : c.charCodeAt(0);
  for (let i = alfanumerico.length - 1; i >= 0; i--) {
    if (k > baseMax) k = 2;
    total += parseInt(alfanumerico.charAt(i), 10) * k++;
  }
  const resto = total % 11;
  return resto > 1 ? 11 - resto : 0;
}

const RE_RUC_DV = /^(\d{3,8})-(\d)$/;

interface Fila {
  fila: number;
  ruc: string;
  dv: string;
  razonSocial: string;
  timbrado: string | null;
  fecha: Date | null;
  tipoContribuyenteId: string;
  avisos: string[];
}

// ══════════════════════════════════════════════════════════════════════════
//  LECTURA Y CONVERSIÓN
// ══════════════════════════════════════════════════════════════════════════
function leerExcel(ruta: string): { filas: Fila[]; descartadas: string[] } {
  const wb = XLSX.readFile(ruta);
  const nombreHoja = HOJA ?? wb.SheetNames[0];
  const hoja = wb.Sheets[nombreHoja];
  if (!hoja) throw new Error(`La hoja "${nombreHoja}" no existe. Hojas: ${wb.SheetNames.join(', ')}`);

  // range:1 → la fila 1 es el título "Proveedores"; el encabezado real es la 2ª.
  const rows: Record<string, any>[] = XLSX.utils.sheet_to_json(hoja, { range: 1, defval: '' });
  const filas: Fila[] = [];
  const descartadas: string[] = [];

  rows.forEach((row, i) => {
    const nroFila = i + 3; // +2 por título y encabezado, +1 porque Excel es 1-indexed
    const documento = col(row, 'C.I. / RUC', 'CI / RUC', 'RUC', 'documento');
    const razonSocial = col(row, 'Nombre', 'razon social', 'razon_social');
    const timbradoRaw = col(row, 'Número del Timbrado', 'Numero del Timbrado', 'timbrado');
    const fecha = fechaExcel(row[Object.keys(row).find((k) => /fecha/i.test(k)) ?? '']);

    if (!documento && !razonSocial) return; // fila vacía o separadora

    if (!documento) {
      descartadas.push(`fila ${nroFila}: "${razonSocial}" sin RUC`);
      return;
    }
    if (razonSocial.length < 4) {
      descartadas.push(`fila ${nroFila}: razón social "${razonSocial}" tiene menos de 4 caracteres (mínimo SIFEN)`);
      return;
    }
    if (razonSocial.length > 255) {
      descartadas.push(`fila ${nroFila}: razón social de ${razonSocial.length} caracteres (máximo 255)`);
      return;
    }

    const m = RE_RUC_DV.exec(documento);
    if (!m) {
      descartadas.push(`fila ${nroFila}: "${documento}" no tiene formato RUC-DV (ej. 80064372-0) — revisar a mano`);
      return;
    }
    const [, ruc, dv] = m;

    const avisos: string[] = [];
    const dvEsperado = calcularDV(ruc);
    if (Number(dv) !== dvEsperado) {
      avisos.push(`DV ${dv} no coincide con el calculado (${dvEsperado}) para el RUC ${ruc}`);
    }

    // `timbrado` es VarChar(8): si viniera algo más largo se descarta el dato,
    // no la fila — el proveedor igual sirve, el timbrado se corrige al comprar.
    let timbrado: string | null = timbradoRaw || null;
    if (timbrado && !/^\d{1,8}$/.test(timbrado)) {
      avisos.push(`timbrado "${timbrado}" no es un número de hasta 8 dígitos — se deja vacío`);
      timbrado = null;
    }

    filas.push({
      fila: nroFila,
      ruc,
      dv,
      razonSocial,
      timbrado,
      fecha,
      tipoContribuyenteId:
        Number(ruc) > 80_000_000 ? CAT.TIPO_CONTRIBUYENTE_JURIDICA : CAT.TIPO_CONTRIBUYENTE_FISICA,
      avisos,
    });
  });

  return { filas, descartadas };
}

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

async function main() {
  if (!RUC_EMPRESA) throw new Error('Falta --ruc=<8 dígitos>. Ej: --ruc=80100092');
  if (!fs.existsSync(XLSX_FILE)) throw new Error(`No existe el archivo: ${XLSX_FILE}`);
  if (!TIPOS_ENTIDAD.includes(TIPO_ENTIDAD as any)) {
    throw new Error(`--tipo-entidad inválido: "${TIPO_ENTIDAD}". Válidos: ${TIPOS_ENTIDAD.join(' | ')}`);
  }

  say('═'.repeat(78));
  say('  IMPORTACIÓN DE PROVEEDORES');
  say('═'.repeat(78));
  say(`  Archivo      : ${XLSX_FILE}`);
  say(`  RUC empresa  : ${RUC_EMPRESA}`);
  say(`  tipo_entidad : ${TIPO_ENTIDAD}`);
  say(`  Modo         : ${DRY ? 'DRY-RUN (no escribe)' : 'REAL'}`);
  say(`  Log          : ${LOG_FILE}`);
  say('═'.repeat(78));

  const empresa = await prisma.empresas.findUnique({
    where: { ruc: RUC_EMPRESA },
    select: { id: true, ruc: true, razon_social: true },
  });
  if (!empresa) throw new Error(`No existe empresa con RUC ${RUC_EMPRESA}`);
  say(`\n▸ Empresa: ${empresa.razon_social} (${empresa.ruc})  id=${empresa.id}\n`);

  // ── 1. Leer ───────────────────────────────────────────────────────────
  const { filas, descartadas } = leerExcel(XLSX_FILE);
  say(`▸ Excel: ${filas.length} filas de comprobante válidas`);
  if (descartadas.length) {
    say(`  ${descartadas.length} filas descartadas:`);
    descartadas.forEach((d) => say(`    ✗ ${d}`));
  }

  // ── 2. Deduplicar por RUC quedándose con la fecha más reciente ────────
  const porRuc = new Map<string, Fila>();
  const timbradosPorRuc = new Map<string, Set<string>>();
  let repetidos = 0;
  for (const f of filas) {
    if (f.timbrado) {
      const set = timbradosPorRuc.get(f.ruc) ?? new Set<string>();
      set.add(f.timbrado);
      timbradosPorRuc.set(f.ruc, set);
    }
    const previa = porRuc.get(f.ruc);
    if (!previa) {
      porRuc.set(f.ruc, f);
      continue;
    }
    repetidos++;
    if ((f.fecha?.getTime() ?? 0) > (previa.fecha?.getTime() ?? 0)) porRuc.set(f.ruc, f);
  }
  const conVariosTimbrados = [...timbradosPorRuc.values()].filter((s) => s.size > 1).length;
  const unicos = [...porRuc.values()].sort((a, b) => a.razonSocial.localeCompare(b.razonSocial));

  say(`  RUC únicos              : ${unicos.length}`);
  say(`  Filas repetidas         : ${repetidos} (se toma la de fecha más reciente)`);
  say(`  RUC con varios timbrados: ${conVariosTimbrados} (se carga el de la fecha más reciente)`);
  const juridicas = unicos.filter((f) => f.tipoContribuyenteId === CAT.TIPO_CONTRIBUYENTE_JURIDICA).length;
  say(`  Personas jurídicas      : ${juridicas}`);
  say(`  Personas físicas        : ${unicos.length - juridicas}`);

  const conAvisos = unicos.filter((f) => f.avisos.length);
  if (conAvisos.length) {
    say(`\n  ⚠ ${conAvisos.length} con avisos (se importan igual):`);
    conAvisos.forEach((f) => f.avisos.forEach((a) => say(`    ⚠ fila ${f.fila} ${f.razonSocial}: ${a}`)));
  }

  // ── 3. Insertar ───────────────────────────────────────────────────────
  say('\n▸ Procesando:');
  let personasCreadas = 0;
  let personasReusadas = 0;
  let proveedoresCreados = 0;
  let proveedoresExistentes = 0;
  const yaEranClientes: string[] = [];
  const errores: string[] = [];

  for (const f of unicos) {
    const etiqueta = `${`${f.ruc}-${f.dv}`.padEnd(11)} ${f.razonSocial}`;
    try {
      // Una persona se comparte entre roles: el mismo RUC puede ya existir como
      // cliente. Se reutiliza en vez de duplicarla.
      const persona = await prisma.personas.findFirst({
        where: { empresa_id: empresa.id, deleted: false, ruc: f.ruc },
        select: { id: true, razon_social: true, clientes: { select: { id: true }, take: 1 } },
      });

      let personaId: string;
      if (persona) {
        personaId = persona.id;
        personasReusadas++;
        if (persona.clientes.length) yaEranClientes.push(`${f.ruc}-${f.dv} ${persona.razon_social}`);
        say(`  ~ ${etiqueta}  → persona ya existía ("${persona.razon_social}")`);
      } else if (DRY) {
        personaId = `dry-${f.ruc}`;
        personasCreadas++;
      } else {
        const creada = await prisma.personas.create({
          data: {
            empresa_id: empresa.id,
            razon_social: f.razonSocial,
            naturaleza_id: CAT.NATURALEZA_CONTRIBUYENTE,
            tipo_contribuyente_id: f.tipoContribuyenteId,
            tipo_documento_id: null,
            ruc: f.ruc,
            dv: f.dv,
            nro_documento: null,
            pais_id: CAT.PAIS_PARAGUAY,
            active: true,
            deleted: false,
          },
          select: { id: true },
        });
        personaId = creada.id;
        personasCreadas++;
      }

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

      const existente = await prisma.proveedores.findFirst({
        where: { empresa_id: empresa.id, persona_id: personaId },
        select: { id: true, timbrado: true },
      });

      if (existente) {
        proveedoresExistentes++;
        // Solo se completa el timbrado si estaba vacío: no se pisa lo que el
        // usuario haya cargado a mano.
        if (!existente.timbrado && f.timbrado) {
          await prisma.proveedores.update({ where: { id: existente.id }, data: { timbrado: f.timbrado } });
          say(`  ~ ${etiqueta}  → proveedor ya existía, se completó timbrado ${f.timbrado}`);
        } else {
          say(`  ~ ${etiqueta}  → proveedor ya existía`);
        }
      } else {
        await prisma.proveedores.create({
          data: {
            empresa_id: empresa.id,
            persona_id: personaId,
            tipo_entidad: TIPO_ENTIDAD,
            es_extranjero: false,
            pais: 'PRY',
            timbrado: f.timbrado,
            // La planilla trae la fecha del comprobante, no el vencimiento
            // del timbrado. Se deja en null antes que cargar un dato falso.
            fecha_vencimiento_timbrado: null,
            activo: true,
          },
        });
        proveedoresCreados++;
      }
    } catch (e: any) {
      errores.push(`fila ${f.fila} ${f.ruc}-${f.dv} ${f.razonSocial}: ${e.message}`);
      say(`  ✗ ${etiqueta}  → ${e.message}`);
    }
  }

  // ── 4. Resumen ────────────────────────────────────────────────────────
  say('\n' + '═'.repeat(78));
  say('  RESUMEN');
  say('═'.repeat(78));
  say(`  Filas del Excel         : ${filas.length + descartadas.length}`);
  say(`  Descartadas             : ${descartadas.length}`);
  say(`  RUC únicos              : ${unicos.length}`);
  say(`  Personas creadas        : ${personasCreadas}`);
  say(`  Personas reutilizadas   : ${personasReusadas}`);
  say(`  Proveedores creados     : ${proveedoresCreados}`);
  say(`  Proveedores existentes  : ${proveedoresExistentes}`);
  say(`  Errores                 : ${errores.length}`);
  errores.forEach((e) => say(`    ✗ ${e}`));

  if (yaEranClientes.length) {
    say(`\n  ℹ ${yaEranClientes.length} proveedores comparten persona con un cliente ya cargado:`);
    yaEranClientes.forEach((x) => say(`      ${x}`));
  }

  say('\n  ⚠ `fecha_vencimiento_timbrado` quedó en null: la planilla trae la fecha del');
  say('    comprobante, no el vencimiento. Se completa al facturar la primera compra.');

  if (DRY) say('\n  (DRY-RUN: no se escribió nada en la base de datos)');
  say('');
}

main()
  .catch((e) => {
    say(`\n✖ ${e.message}\n`);
    process.exitCode = 1;
  })
  .finally(async () => {
    logStream.end();
    await prisma.$disconnect();
  });
