/**
 * Importa el listado de clientes de ALISABE S.A. desde Excel.
 *
 *   Empresa : 75e91209-8a96-4a1e-9e65-994b0368ee3a (ALISABE SOCIEDAD ANONIMA)
 *   Fuente  : Listado_clientes.xlsx, hoja "Clientes"
 *
 * Está modelado sobre `scripts/data_script/import-clientes.ts`, con una
 * diferencia central: **no consulta SIFEN**. Aquel script preguntaba a mdware
 * si el documento era contribuyente para decidir naturaleza y tipo. Acá no hace
 * falta porque la planilla ya trae el RUC con su dígito verificador
 * ("80063608-2"): basta partir por el guión, y el hecho de que tenga DV es la
 * evidencia de que está inscripto. Eso evita 392 llamadas de red y quita la
 * dependencia de que mdware esté arriba.
 *
 *   Dry-run (por defecto):
 *     pnpm import:clientes:alisabe
 *   Aplicar de verdad:
 *     pnpm import:clientes:alisabe --apply
 *
 * Se puede pisar el origen y el destino por entorno, útil para correrlo en otro
 * servidor donde el .xlsx esté en otra ruta:
 *     XLSX_FILE=/ruta/al/Listado_clientes.xlsx pnpm import:clientes:alisabe
 *
 * Es idempotente: si ya existe una persona con ese RUC en la empresa, no la
 * duplica; si la persona existe pero no tiene cliente, le crea sólo el cliente.
 */

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

// ══════════════════════════════════════════════════════════════════
//  CONFIG
// ══════════════════════════════════════════════════════════════════
const CONFIG = {
  // Ambos se pueden pisar por entorno para correrlo en otro servidor sin tocar
  // el archivo: el .xlsx no está versionado, así que en prod hay que copiarlo
  // y apuntarle con XLSX_FILE.
  EMPRESA_ID: process.env.EMPRESA_ID || '75e91209-8a96-4a1e-9e65-994b0368ee3a', // ALISABE SOCIEDAD ANONIMA

  XLSX_FILE:
    process.env.XLSX_FILE ||
    path.resolve(__dirname, '../data_script/cliente-agogo/estacion/Listado_clientes.xlsx'),
  HOJA: 'Clientes',

  // Catálogos globales (no dependen de la empresa)
  NATURALEZA_CONTRIBUYENTE_ID: '45f32973-7d88-4d47-b2ea-d63b4d448d54',
  TIPO_CONTRIBUYENTE_JURIDICA_ID: 'b0da490e-ff65-4c8f-a5e5-823e17a67e9e', // RUC > 80.000.000
  TIPO_CONTRIBUYENTE_FISICA_ID: 'da9fdf2b-d72c-4bb7-8689-526a7f846c83',
  TIPO_OPERACION_B2B_ID: '886b4cb8-17d8-443b-bb00-77664604ed01',
  PAIS_PARAGUAY_ID: '253381af-b7e6-4c2f-9e90-33be1aea687b',

  LIMITE_CREDITO_DEFAULT: 0,

  /**
   * RUCs a dejar afuera. Son comodines del sistema viejo, no clientes reales:
   * ninguno de los dos es un RUC válido y facturarles haría que SIFEN rechace.
   * Para eso la empresa ya tiene su cliente innominado ("Sin nombre", SN001).
   */
  RUCS_A_EXCLUIR: [
    '99999999', // MÚLTIPLES CLIENTES
    '44444401', // OCASIONAL
  ] as string[],
};

// La planilla trae los mails en la columna BARRIO — no en "e-MAIL", que está
// vacía en las 417 filas. Se leen las dos por si el archivo cambia.
const COLS = {
  codigo: ['CODIGO', 'CÓDIGO', 'COD'],
  razonSocial: ['RAZÓN SOCIAL', 'RAZON SOCIAL', 'NOMBRE'],
  ruc: ['RUC'],
  ci: ['CI'],
  direccion: ['DIRECCION', 'DIRECCIÓN'],
  telefono: ['TELEFONO', 'TELÉFONO'],
  email: ['e-MAIL', 'E-MAIL', 'EMAIL', 'BARRIO'],
};

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

const txt = (v: any): string => String(v ?? '').trim();

/** Lee una columna probando varios nombres, sin distinguir mayúsculas ni espacios. */
function colVal(row: any, names: string[]): string {
  for (const n of names) if (txt(row[n])) return txt(row[n]);
  const norm = (s: string) => s.toLowerCase().replace(/\s+/g, ' ').trim();
  for (const n of names) {
    const k = Object.keys(row).find((k2) => norm(k2) === norm(n));
    if (k && txt(row[k])) return txt(row[k]);
  }
  return '';
}

/**
 * Parte "80063608-2" en { ruc: "80063608", dv: "2" }.
 *
 * El DV sale del archivo, no se calcula: es el que el cliente viene usando en
 * sus documentos reales. Igual se compara contra el módulo 11 para reportar
 * discrepancias, pero manda el archivo.
 */
function partirRuc(valor: string): { ruc: string; dv: string } | null {
  const limpio = valor.replace(/[\s.]/g, '');
  if (!limpio) return null;
  const [numero, verificador] = limpio.split('-');
  const ruc = (numero ?? '').replace(/\D/g, '');
  const dv = (verificador ?? '').replace(/\D/g, '');
  if (!ruc || !dv) return null;
  if (ruc.length > 8) return null; // personas.ruc es varchar(8)
  return { ruc, dv: dv.slice(0, 1) };
}

/** Dígito verificador módulo 11, sólo para señalar los que no coinciden. */
function dvModulo11(numero: string, baseMax = 11): number {
  let total = 0;
  let k = 2;
  let alfa = '';
  for (const c of numero) alfa += /\d/.test(c) ? c : c.charCodeAt(0);
  for (let i = alfa.length - 1; i >= 0; i--) {
    k = k > baseMax ? 2 : k;
    total += parseInt(alfa.charAt(i), 10) * k++;
  }
  const resto = total % 11;
  return resto > 1 ? 11 - resto : 0;
}

/** personas.telefono es varchar(15): se deja sólo lo que entra. */
function sanitizarTelefono(v: string): string | null {
  const solo = v.replace(/[^\d+]/g, '');
  return solo ? solo.slice(0, 15) : null;
}

function sanitizarEmail(v: string): string | null {
  const e = v.toLowerCase();
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)) return null;
  return e.slice(0, 80); // personas.email es varchar(80)
}

// ══════════════════════════════════════════════════════════════════
//  LOG
// ══════════════════════════════════════════════════════════════════
const APLICAR = process.argv.includes('--apply');
const sello = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const LOG_FILE = path.join(__dirname, `import-clientes-alisabe-${sello}.log`);
const logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });

function out(linea: string) {
  console.log(linea);
  logStream.write(linea + '\n');
}

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

interface Fila {
  excelFila: number;
  codigo: string;
  razonSocial: string;
  ruc: string;
  dv: string;
  direccion: string | null;
  telefono: string | null;
  email: string | null;
}

async function run() {
  out('');
  out('╔════════════════════════════════════════════════════════════╗');
  out(`║  CLIENTES ALISABE → Novasis   ${(APLICAR ? '[ APLICA — ESCRIBE ]' : '[ DRY RUN ]').padEnd(28)}║`);
  out('╚════════════════════════════════════════════════════════════╝');
  out('');

  const empresa = await prisma.empresas.findUnique({
    where: { id: CONFIG.EMPRESA_ID },
    select: { id: true, razon_social: true, ruc: true },
  });
  if (!empresa) {
    out(`✗ La empresa ${CONFIG.EMPRESA_ID} no existe.`);
    return;
  }
  out(`Empresa destino : ${empresa.razon_social} (RUC ${empresa.ruc})`);
  out(`Archivo         : ${CONFIG.XLSX_FILE}`);

  if (!fs.existsSync(CONFIG.XLSX_FILE)) {
    out(`✗ No se encontró el archivo.`);
    return;
  }

  const wb = XLSX.readFile(CONFIG.XLSX_FILE);
  const hoja = wb.Sheets[CONFIG.HOJA] ?? wb.Sheets[wb.SheetNames[0]];
  const rows: any[] = XLSX.utils.sheet_to_json(hoja, { defval: null, raw: false });
  out(`Filas en Excel  : ${rows.length}`);
  out('');

  // ── Parseo ──────────────────────────────────────────────────────
  const filas: Fila[] = [];
  const sinRuc: string[] = [];
  const rucInvalido: string[] = [];
  const dvDiscrepante: string[] = [];
  const excluidos: string[] = [];
  const sospechosos: string[] = [];
  const esLaPropiaEmpresa: string[] = [];

  for (let i = 0; i < rows.length; i++) {
    const row = rows[i];
    const excelFila = i + 2; // +1 por índice base 0, +1 por la cabecera
    const razonSocial = colVal(row, COLS.razonSocial);
    // El RUC manda; la columna CI trae lo mismo cuando está cargada.
    const rucCrudo = colVal(row, COLS.ruc) || colVal(row, COLS.ci);

    if (!rucCrudo) {
      sinRuc.push(`fila ${excelFila}: ${razonSocial || '(sin nombre)'}`);
      continue;
    }

    // Sin RUC no se migra: no hay con qué identificar a la persona ni forma de
    // emitirle un comprobante después.
    const partido = partirRuc(rucCrudo);
    if (!partido) {
      rucInvalido.push(`fila ${excelFila}: "${rucCrudo}" — ${razonSocial}`);
      continue;
    }

    if (CONFIG.RUCS_A_EXCLUIR.includes(partido.ruc)) {
      excluidos.push(`${partido.ruc}-${partido.dv} — ${razonSocial}`);
      continue;
    }

    const calculado = String(dvModulo11(partido.ruc));
    if (calculado !== partido.dv) {
      dvDiscrepante.push(`${partido.ruc}-${partido.dv} (módulo 11 daría ${calculado}) — ${razonSocial}`);
    }
    if (/OCASIONAL|MÚLTIPLES|MULTIPLES|VARIOS|CONSUMIDOR FINAL|SIN NOMBRE/i.test(razonSocial)) {
      sospechosos.push(`${partido.ruc}-${partido.dv} — ${razonSocial}`);
    }
    if (partido.ruc === empresa.ruc) {
      esLaPropiaEmpresa.push(`${partido.ruc}-${partido.dv} — ${razonSocial}`);
    }

    filas.push({
      excelFila,
      codigo: colVal(row, COLS.codigo).slice(0, 15), // clientes.cod_cliente es varchar(15)
      razonSocial: razonSocial || `Cliente ${partido.ruc}`,
      ruc: partido.ruc,
      dv: partido.dv,
      direccion: colVal(row, COLS.direccion) || null,
      telefono: sanitizarTelefono(colVal(row, COLS.telefono)),
      email: sanitizarEmail(colVal(row, COLS.email)),
    });
  }

  // ── Deduplicación por RUC ───────────────────────────────────────
  // Se queda la primera aparición y se completan los datos de contacto con las
  // siguientes: en esta planilla las repetidas traen el mismo nombre pero a
  // veces una sola de ellas tiene el mail o el teléfono.
  const porRuc = new Map<string, Fila>();
  const duplicados: string[] = [];
  for (const f of filas) {
    const previa = porRuc.get(f.ruc);
    if (!previa) {
      porRuc.set(f.ruc, f);
      continue;
    }
    duplicados.push(`${f.ruc}-${f.dv} — ${f.razonSocial} (fila ${f.excelFila}, ya vista en ${previa.excelFila})`);
    previa.direccion ??= f.direccion;
    previa.telefono ??= f.telefono;
    previa.email ??= f.email;
    if (!previa.codigo) previa.codigo = f.codigo;
  }

  out('━━━ LECTURA ━━━');
  out(`  Filas con RUC utilizable : ${filas.length}`);
  out(`  RUCs únicos a procesar   : ${porRuc.size}`);
  out(`  Sin RUC (se omiten)      : ${sinRuc.length}`);
  out(`  RUC con formato inválido : ${rucInvalido.length}`);
  out(`  Repetidos en la planilla : ${duplicados.length}`);
  out('');

  const bloque = (titulo: string, lista: string[]) => {
    if (!lista.length) return;
    out(`── ${titulo} (${lista.length})`);
    lista.forEach((l) => out(`   • ${l}`));
    out('');
  };

  bloque('SIN RUC — no se importan', sinRuc);
  bloque('RUC CON FORMATO INVÁLIDO — no se importan', rucInvalido);
  bloque('REPETIDOS EN LA PLANILLA — se importa una sola vez', duplicados);
  bloque('DV QUE NO COINCIDE CON EL MÓDULO 11 — se usa el del archivo', dvDiscrepante);
  bloque('PARECEN COMODINES, NO CLIENTES REALES — revisar antes de aplicar', sospechosos);
  bloque('ES LA PROPIA EMPRESA CARGADA COMO CLIENTE', esLaPropiaEmpresa);
  bloque('EXCLUIDOS POR CONFIG', excluidos);

  // ── Alta ────────────────────────────────────────────────────────
  out('━━━ PROCESANDO ━━━');

  let personasCreadas = 0;
  let personasExistentes = 0;
  let clientesCreados = 0;
  let clientesExistentes = 0;
  let juridicas = 0;
  let fisicas = 0;
  const errores: string[] = [];

  const entradas = [...porRuc.values()].sort((a, b) => a.razonSocial.localeCompare(b.razonSocial, 'es'));

  for (let i = 0; i < entradas.length; i++) {
    const f = entradas[i];
    const esJuridica = parseInt(f.ruc, 10) > 80_000_000;
    esJuridica ? juridicas++ : fisicas++;

    const marca = `[${String(i + 1).padStart(3)}/${entradas.length}]`;
    const etiqueta = esJuridica ? 'JURÍDICA' : 'FÍSICA  ';
    const contacto = [f.telefono && `tel ${f.telefono}`, f.email && `mail ${f.email}`].filter(Boolean).join(' ');

    if (!APLICAR) {
      out(`${marca} ${etiqueta} | ${f.razonSocial.substring(0, 42).padEnd(42)} | ${f.ruc}-${f.dv} ${contacto}`);
      continue;
    }

    try {
      let persona = await prisma.personas.findFirst({
        where: { empresa_id: CONFIG.EMPRESA_ID, ruc: f.ruc, deleted: false },
      });

      if (persona) {
        personasExistentes++;
      } else {
        persona = await prisma.personas.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            razon_social: f.razonSocial,
            naturaleza_id: CONFIG.NATURALEZA_CONTRIBUYENTE_ID,
            tipo_contribuyente_id: esJuridica
              ? CONFIG.TIPO_CONTRIBUYENTE_JURIDICA_ID
              : CONFIG.TIPO_CONTRIBUYENTE_FISICA_ID,
            // Un contribuyente se identifica por RUC: el tipo de documento de
            // identidad y nro_documento quedan nulos a propósito.
            tipo_documento_id: null,
            nro_documento: null,
            ruc: f.ruc,
            dv: f.dv,
            pais_id: CONFIG.PAIS_PARAGUAY_ID,
            direccion: f.direccion,
            telefono: f.telefono,
            email: f.email,
            active: true,
            deleted: false,
          },
        });
        personasCreadas++;
      }

      const cliente = await prisma.clientes.findFirst({
        where: { persona_id: persona.id, deleted: false },
      });

      if (cliente) {
        clientesExistentes++;
        out(`${marca} ~ ya existía | ${f.razonSocial.substring(0, 42)}`);
      } else {
        const nuevo = await prisma.clientes.create({
          data: {
            persona_id: persona.id,
            tipo_operacion_id: CONFIG.TIPO_OPERACION_B2B_ID,
            cod_cliente: f.codigo || null,
            referencia_domicilio: f.direccion,
            limite_credito: CONFIG.LIMITE_CREDITO_DEFAULT,
            active: true,
            deleted: false,
          },
        });
        clientesCreados++;
        out(`${marca} ✓ ${etiqueta} | ${f.razonSocial.substring(0, 42).padEnd(42)} | ${f.ruc}-${f.dv} | ${nuevo.id}`);
      }
    } catch (e: any) {
      const msg = `${f.ruc}-${f.dv} | ${f.razonSocial} | ${e.message}`;
      errores.push(msg);
      out(`${marca} ✗ ${msg}`);
    }
  }

  // ── Resumen ─────────────────────────────────────────────────────
  out('');
  out('╔════════════════════════════════════════════════════════════╗');
  out('║                      RESUMEN                               ║');
  out('╚════════════════════════════════════════════════════════════╝');
  out(`  Filas en Excel            : ${String(rows.length).padStart(5)}`);
  out(`  Sin RUC / RUC inválido    : ${String(sinRuc.length + rucInvalido.length).padStart(5)}`);
  out(`  Repetidos en la planilla  : ${String(duplicados.length).padStart(5)}`);
  out(`  RUCs únicos               : ${String(porRuc.size).padStart(5)}`);
  out(`    · personas jurídicas    : ${String(juridicas).padStart(5)}`);
  out(`    · personas físicas      : ${String(fisicas).padStart(5)}`);
  if (APLICAR) {
    out('  ─────────────────────────────────');
    out(`  Personas creadas          : ${String(personasCreadas).padStart(5)}`);
    out(`  Personas ya existentes    : ${String(personasExistentes).padStart(5)}`);
    out(`  Clientes creados          : ${String(clientesCreados).padStart(5)}`);
    out(`  Clientes ya existentes    : ${String(clientesExistentes).padStart(5)}`);
    out(`  Errores                   : ${String(errores.length).padStart(5)}`);
  }
  out('');

  if (errores.length) {
    out('── ERRORES');
    errores.forEach((e, i) => out(`   [${i + 1}] ${e}`));
    out('');
  }

  out(
    APLICAR
      ? errores.length === 0
        ? '✓  Importación completada sin errores.'
        : `⚠  Importación completada con ${errores.length} error(es).`
      : '⚠  DRY RUN — no se escribió nada. Ejecutar con --apply para aplicar.',
  );
  out(`\nLog: ${LOG_FILE}\n`);
}

run()
  .catch((e) => {
    out(`✗✗ FATAL: ${e.message}\n${e.stack}`);
    process.exitCode = 1;
  })
  .finally(async () => {
    logStream.end();
    await prisma.$disconnect();
  });
