#!/usr/bin/env node
/*
  SENIAT RIF CLI (dos pasos)
  - Paso 1: descarga la página, obtiene cookies y guarda el captcha como archivo local
  - Paso 2: toma el RIF y el código del captcha, envía el formulario y extrae los datos

  Uso:
    # Interactivo (te pedirá RIF y código tras guardar la imagen)
    npm run rif:cli  --prefix .

    # No interactivo (banderas)
    node rif-cli.js --rif J299036544 --codigo abc123

  Salida:
    - Archivo captcha: ./captcha-<sessionId>.jpg
    - JSON con rifData: { rif, nombre, actividad_economica, condicion_iva, retencion_iva }
*/

const fs = require('fs');
const path = require('path');
const os = require('os');
// node-fetch v3 es ESM; soportamos require o import dinámico
let fetch;
try {
  fetch = require('node-fetch');
  if (fetch && fetch.default) fetch = fetch.default;
} catch (_) {
  fetch = (...args) => import('node-fetch').then(m => m.default(...args));
}
const cheerio = require('cheerio');
const { JSDOM } = require('jsdom');
const readline = require('readline');
const { spawn } = require('child_process');

const BASE = 'http://contribuyente.seniat.gob.ve/BuscaRif';

function normalizeRifValue(raw) {
  const value = String(raw || '').trim().toUpperCase();
  if (!value) return null;
  const letter = value.charAt(0);
  const digits = value.slice(1).replace(/\D/g, '');
  if (!letter || !digits) return null;
  return `${letter}${digits.padStart(9, '0')}`;
}

function makeCookieHeader(setCookieArray) {
  if (!setCookieArray) return '';
  const arr = Array.isArray(setCookieArray) ? setCookieArray : [setCookieArray];
  return arr.map(s => s.split(';')[0]).join('; ');
}

// Extrae el porcentaje de retención del texto, tolerando acentos y espacios
function extractRetencionPercent(text) {
  if (!text) return null;
  const candidates = [];
  // 1) Texto tal cual
  const m1 = text.match(/retención\s+del\s*(\d{1,3})\s*%/i);
  if (m1 && m1[1]) candidates.push(`${m1[1]}%`);
  // 2) Texto normalizado (sin acentos)
  const norm = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  const m2 = norm.match(/retencion\s+del\s*(\d{1,3})\s*%/i);
  if (m2 && m2[1]) candidates.push(`${m2[1]}%`);
  // 3) Fallback amplio: cualquier porcentaje cerca de "retencion"
  const m3 = norm.match(/retencion[^%]*?(\d{1,3})\s*%/i);
  if (m3 && m3[1]) candidates.push(`${m3[1]}%`);
  // 4) Fallback final: primer porcentaje en el bloque (por si el texto empieza en "0%" o similar)
  const m4 = norm.match(/(\d{1,3})\s*%/);
  if (m4 && m4[1]) candidates.push(`${m4[1]}%`);
  return candidates.length ? candidates[0] : null;
}

// Extrae Actividad Económica y Condición IVA de un bloque de texto/HTML ya "flatenizado"
function extractActividadCondicion(text) {
  if (!text) return { actividad_economica: null, condicion_iva: null };
  // Trabajamos con dos vistas: original y normalizada (sin acentos)
  const flat = String(text).replace(/<br\s*\/?>(\s)*/gi, ' ').replace(/&nbsp;/gi, ' ').replace(/\s\s+/g, ' ').trim();
  const norm = flat.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

  // Actividad: entre "Actividad Económica:" y "Condición:" (con/ sin acentos, tolera espacios alrededor de :) 
  let actividad = null;
  let mAct = flat.match(/Actividad\s+Económica\s*:?[\s\u00A0]*([^]*?)\s*Condición(?:\s+(?:del\s+)?IVA)?\s*:/i);
  if (!(mAct && mAct[1])) mAct = norm.match(/Actividad\s+Economica\s*:?[\s\u00A0]*([^]*?)\s*Condicion(?:\s+(?:del\s+)?IVA)?\s*:/i);
  if (mAct && mAct[1]) actividad = mAct[1].trim();
  if (actividad && actividad.toUpperCase() === 'INFORMACION NO DISPONIBLE') actividad = null;

  // Condición: desde "Condición:" hasta antes de la leyenda o fin de bloque
  let condicion = null;
  let mCond = flat.match(/Condición(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condición\s+de\s+este\s+contribuyente|$)/i);
  if (!(mCond && mCond[1])) mCond = norm.match(/Condicion(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condicion\s+de\s+este\s+contribuyente|$)/i);
  if (mCond && mCond[1]) condicion = mCond[1].trim().replace(/\s+/g, ' ');

  return { actividad_economica: actividad, condicion_iva: condicion };
}

function extractWithDomScript(html) {
  // Evalúa la lógica exacta del usuario sobre un DOM real (JSDOM)
  const dom = new JSDOM(html);
  const { document } = dom.window;
  try {
    function extraerDatosRIF_Limpio_fromDOM(document) {
      const datosRIF = {
        rif: 'NO ENCONTRADO',
        nombre: 'NO ENCONTRADO',
        actividad_economica: 'NO ENCONTRADO',
        condicion_iva: 'NO ENCONTRADO',
        retencion_iva: '0%'
      };
      const elementoRIFNombre = document.querySelector('table[align="center"] font[face="Verdana"][size="2"]');
      if (elementoRIFNombre) {
        const textoCompleto = elementoRIFNombre.textContent.trim();
        const match = textoCompleto.match(/^(\w+)\s+(.+)$/);
        if (match && match.length === 3) {
          datosRIF.rif = match[1];
          datosRIF.nombre = match[2];
        }
      }
      const elementoDetalleTable = document.querySelectorAll('table')[2];
      const elementoDetalle = elementoDetalleTable ? elementoDetalleTable.querySelector('font[face="Verdana"][size="1"]') : null;
      if (elementoDetalle) {
            let textoLimpio = elementoDetalle.innerHTML
              .replace(/<br>/gi, ' ')
              .replace(/&nbsp;/gi, ' ')
              .replace(/\s\s+/g, ' ')
              .trim();
            // Actividad (con acentos y fallback sin acentos)
            let matchActividad = textoLimpio.match(/Actividad Económica\s*:?[\s\u00A0]*([^]*?)\s*Condición(?:\s+(?:del\s+)?IVA)?\s*:/i);
            if (!(matchActividad && matchActividad[1])) {
              const norm = textoLimpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
              matchActividad = norm.match(/Actividad Economica\s*:?[\s\u00A0]*([^]*?)\s*Condicion(?:\s+(?:del\s+)?IVA)?\s*:/i);
              if (matchActividad && matchActividad[1]) textoLimpio = norm;
            }
            if (matchActividad && matchActividad[1] && matchActividad[1].trim().toUpperCase() !== 'INFORMACION NO DISPONIBLE') {
              datosRIF.actividad_economica = matchActividad[1].trim();
            }
            // Condición (capturar hasta antes de la leyenda)
            let matchCondicion = textoLimpio.match(/Condición(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La condición de este contribuyente|$)/i);
            if (!(matchCondicion && matchCondicion[1])) {
              const norm = textoLimpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
              matchCondicion = norm.match(/Condicion(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La condicion de este contribuyente|$)/i);
              if (matchCondicion && matchCondicion[1]) textoLimpio = norm;
            }
            if (matchCondicion && matchCondicion[1]) {
              datosRIF.condicion_iva = matchCondicion[1].trim().replace(/\s+/g, ' ');
            }
            // Retención (tolerante y flexible)
            const ret = extractRetencionPercent(textoLimpio);
            if (ret) datosRIF.retencion_iva = ret;
      }
          // Fallback general: si faltan actividad o condición, usa todo el cuerpo
          if (!datosRIF.actividad_economica || !datosRIF.condicion_iva) {
            const bodyHTML = (document.body && document.body.innerHTML) ? document.body.innerHTML : document.documentElement.innerHTML;
            const { actividad_economica, condicion_iva } = extractActividadCondicion(bodyHTML);
            if (!datosRIF.actividad_economica) datosRIF.actividad_economica = actividad_economica;
            if (!datosRIF.condicion_iva) datosRIF.condicion_iva = condicion_iva;
          }
      return datosRIF;
    }
    return extraerDatosRIF_Limpio_fromDOM(document);
  } catch (_) {
    return null;
  }
}

function extractRifDetails(html) {
  const $ = cheerio.load(html);
  try {
    // 1) RIF + Nombre: replica el selector del script del usuario y agrega fallback
    let rif = null;
    let nombre = null;
    let nameText = ($('table[align="center"] font[face="Verdana"][size="2"]').first().text() || '')
      .replace(/\s+/g, ' ')
      .trim();
    if (!nameText) {
      const tNameFallback = $('form#consulta').nextAll('table').eq(0);
      nameText = (tNameFallback.find('font[face="Verdana"][size="2"]').first().text() || '')
        .replace(/\s+/g, ' ')
        .trim();
    }
    const m = nameText.match(/^([VEJGCP]\d{8,})\s+(.+)$/i);
    if (m) {
      rif = m[1];
      nombre = m[2];
    }

    // 2) Detalle actividad/condición/retención: tercera tabla o fuente que contenga las claves
    let fontDetail = $('table').eq(2).find('font[face="Verdana"][size="1"]').first();
    if (!fontDetail || fontDetail.length === 0) {
      const candidates = $('font[face="Verdana"][size="1"]').toArray();
      fontDetail = null;
      for (const el of candidates) {
        const raw = $(el).html() || $(el).text() || '';
        const rawNorm = raw.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
        if (rawNorm.includes('actividad economica') || rawNorm.includes('condicion')) {
          fontDetail = $(el);
          break;
        }
      }
    }

    let actividad_economica = null;
    let condicion_iva = null;
    let retencion_iva = null;
    if (fontDetail && fontDetail.length) {
      const detailHtml = fontDetail.html() || '';
      const limpio = detailHtml
        .replace(/<br\s*\/?>(\s)*/gi, ' ')
        .replace(/&nbsp;/gi, ' ')
        .replace(/\s\s+/g, ' ')
        .trim();
      const norm = limpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

  const actividadMatch = norm.match(/Actividad\s+Economica\s*:?[\s\u00A0]*([^]*?)\s*Condicion(?:\s+(?:del\s+)?IVA)?\s*:/i);
  const condicionMatch = norm.match(/Condicion(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condicion\s+de\s+este\s+contribuyente|$)/i);
      const retencion = extractRetencionPercent(limpio) || extractRetencionPercent(norm);
      const actividadTmp = actividadMatch && actividadMatch[1] ? actividadMatch[1].trim() : null;
      if (actividadTmp && actividadTmp.toUpperCase() !== 'INFORMACION NO DISPONIBLE') actividad_economica = actividadTmp;
      condicion_iva = condicionMatch && condicionMatch[1] ? condicionMatch[1].trim() : null;
      retencion_iva = retencion || null;
    }
    // Fallback: si faltan actividad o condición, parsear sobre todo el HTML
    if (!actividad_economica || !condicion_iva) {
      const wholeHTML = $.root().html() || '';
      const ac = extractActividadCondicion(wholeHTML);
      if (!actividad_economica) actividad_economica = ac.actividad_economica;
      if (!condicion_iva) condicion_iva = ac.condicion_iva;
    }

    const hasAny = rif || nombre || actividad_economica || condicion_iva || retencion_iva;
    if (!hasAny) return null;
    return { rif, nombre, actividad_economica, condicion_iva, retencion_iva };
  } catch (_) {
    return null;
  }
}

async function getSessionAndCaptcha() {
  const pageRes = await fetch(`${BASE}/BuscaRif.jsp`, { method: 'GET' });
  if (!pageRes.ok) throw new Error('No se pudo iniciar sesión');
  const setCookie = pageRes.headers.raw ? pageRes.headers.raw()['set-cookie'] : pageRes.headers.get('set-cookie');
  const cookieHeader = makeCookieHeader(setCookie);

  const rc = await fetch(`${BASE}/Captcha.jpg`, { method: 'GET', headers: { Cookie: cookieHeader } });
  if (!rc.ok) throw new Error('No se pudo obtener captcha');
  const buf = await rc.buffer();
  const sessionId = String(Date.now()) + Math.random().toString(36).slice(2, 8);
  const outPath = path.resolve(__dirname, `captcha-${sessionId}.jpg`);
  fs.writeFileSync(outPath, buf);
  return { sessionId, cookie: cookieHeader, captchaPath: outPath };
}

async function submitRif({ cookie, rif, codigo }) {
  const normalizedRif = normalizeRifValue(rif);
  if (!normalizedRif) throw new Error('RIF inválido');
  const normalizedCodigo = String(codigo || '').trim();
  if (!normalizedCodigo) throw new Error('Código captcha requerido');

  const form = new URLSearchParams();
  form.append('p_rif', normalizedRif);
  form.append('codigo', normalizedCodigo);
  form.append('busca', ' Buscar ');

  const r = await fetch(`${BASE}/BuscaRif.jsp`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Cookie': cookie,
      'Referer': `${BASE}/BuscaRif.jsp`
    },
    body: form.toString()
  });
  if (!r.ok) throw new Error('Error del servidor SENIAT');
  const html = await r.text();
  const rifData = extractRifDetails(html) || {};
  // Intento 1: ejecutar el script exactamente como en el navegador sobre JSDOM
  const domData = extractWithDomScript(html);
  const merged = Object.assign({}, rifData || {}, domData || {});
  return { html, rifData: merged };
}

function parseArgs() {
  const args = process.argv.slice(2);
  const out = {};
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === '--rif') out.rif = args[++i];
    else if (a === '--codigo') out.codigo = args[++i];
    else if (a === '--file' || a === '--html') out.file = args[++i];
    else if (a === '--print-html') out.printHtml = true;
    else if (a === '--include-html-json') out.includeHtmlJson = true;
    else if (a === '--save-html-temp') out.saveHtmlTemp = true;
    else if (a === '--open-captcha') out.openCaptcha = true;
    else if (a === '--save-html') {
      // If next token looks like a path (doesn't start with --), consume it; else use default path later
      const next = args[i + 1];
      if (next && !next.startsWith('--')) {
        out.saveHtml = next;
        i++;
      } else {
        out.saveHtml = true; // flag only; will resolve to default path
      }
    }
  }
  return out;
}

async function prompt(question) {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const answer = await new Promise(resolve => rl.question(question, ans => resolve(ans)));
  rl.close();
  return answer;
}

(async () => {
  try {
    const args = parseArgs();
    if (args.file) {
      const filePath = path.resolve(process.cwd(), args.file);
      const html = fs.readFileSync(filePath, 'utf8');
      const rifDataFromCheerio = extractRifDetails(html) || {};
      const rifDataFromDom = extractWithDomScript(html) || {};
      const rifData = Object.assign({}, rifDataFromCheerio, rifDataFromDom);
      console.table(rifData);
      console.log(JSON.stringify({ ok: true, rifData, mode: 'file' }, null, 2));
      return;
    }

    console.log('Iniciando sesión y descargando captcha...');
    const { sessionId, cookie, captchaPath } = await getSessionAndCaptcha();
    console.log(`Captcha guardado en: ${captchaPath}`);
    if (args.openCaptcha) {
      try {
        if (process.platform === 'darwin') {
          spawn('open', [captchaPath], { detached: true, stdio: 'ignore' }).unref();
        } else if (process.platform === 'win32') {
          spawn('cmd', ['/c', 'start', '', captchaPath], { detached: true, stdio: 'ignore' }).unref();
        } else {
          spawn('xdg-open', [captchaPath], { detached: true, stdio: 'ignore' }).unref();
        }
      } catch (e) {
        console.error('No se pudo abrir la imagen del captcha automáticamente:', e.message || e);
      }
    }

    const rif = args.rif || await prompt('RIF (ej: J299036544): ');
    const codigo = args.codigo || await prompt('Código del captcha (texto en la imagen): ');

    console.log('Enviando datos al SENIAT...');
    const { html, rifData } = await submitRif({ cookie, rif, codigo });

    // Opcional: guardar HTML a archivo si se solicitó
    if (args.saveHtml !== undefined) {
      const outPath = typeof args.saveHtml === 'string'
        ? path.resolve(process.cwd(), args.saveHtml)
        : path.resolve(__dirname, `seniat-response-${Date.now()}.html`);
      try {
        fs.writeFileSync(outPath, html);
        console.log(`HTML completo guardado en: ${outPath}`);
      } catch (e) {
        console.error('No se pudo guardar el HTML:', e.message || e);
      }
    }

    // Opcional: guardar HTML en archivo temporal del sistema
    let tempHtmlPath = null;
    if (args.saveHtmlTemp) {
      try {
        const tmpDir = os.tmpdir();
        tempHtmlPath = path.join(tmpDir, `seniat-response-${Date.now()}-${Math.random().toString(36).slice(2,8)}.html`);
        fs.writeFileSync(tempHtmlPath, html);
        console.log(`HTML temporal guardado en: ${tempHtmlPath}`);
      } catch (e) {
        console.error('No se pudo guardar el HTML temporal:', e.message || e);
      }
    }

    // Opcional: imprimir HTML completo a stdout (puede ser extenso)
    if (args.printHtml) {
      console.log('\n----- HTML SENIAT (inicio) -----');
      console.log(html);
      console.log('----- HTML SENIAT (fin) -----\n');
    }

    console.log('Resultado:');
    console.table(rifData);
    const result = { ok: true, rifData };
    if (args.includeHtmlJson) result.html = html;
    if (tempHtmlPath) result.htmlFile = tempHtmlPath;
    console.log(JSON.stringify(result, null, 2));
  } catch (err) {
    console.error('Error:', err.message || err);
    process.exitCode = 1;
  }
})();
