/**
 * ╔══════════════════════════════════════════════════════════════╗
 * ║  ENRIQUECER EXCEL — agrega razón social vía API SIFEN/mdware ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Lee:    campaña_fe_contactos.xlsx                           ║
 * ║  Escribe: campaña_fe_contactos_enriquecido.xlsx              ║
 * ║                                                              ║
 * ║  Columnas que agrega al final:                               ║
 * ║    • Razon Social                                            ║
 * ║    • Estado Consulta  (CONTRIBUYENTE / NO_CONTRIBUYENTE /    ║
 * ║                        ERROR / SIN_RUC)                      ║
 * ║    • Cod Respuesta                                           ║
 * ║    • Mensaje                                                 ║
 * ║                                                              ║
 * ║  Flags:                                                      ║
 * ║    --dry          no llama a la API (sólo simula)            ║
 * ║    --limit N      procesa sólo las primeras N filas          ║
 * ║    --resume       retoma desde donde quedó el archivo de     ║
 * ║                   salida (no reconsulta RUCs ya enriquecidos)║
 * ║    --delay MS     ms entre consultas (default 80)            ║
 * ╚══════════════════════════════════════════════════════════════╝
 */

import * as fs from 'fs';
import * as http from 'http';
import * as https from 'https';
import * as path from 'path';
import * as XLSX from 'xlsx';

const httpRequest = (url: URL) =>
  url.protocol === 'https:' ? https.request.bind(https) : http.request.bind(http);

// ══════════════════════════════════════════════════════════════════
//  CONFIG
// ══════════════════════════════════════════════════════════════════
const CONFIG = {
  XLSX_IN: path.resolve(__dirname, 'campaña_fe_contactos.xlsx'),
  XLSX_OUT: path.resolve(__dirname, 'campaña_fe_contactos_enriquecido.xlsx'),
  SHEET_NAME: 'Campaña Contactos',

  MDWARE_BASE_URL: 'https://mdware.novasispy.com',
  MDWARE_RUC: '80144315',
  MDWARE_PASSWORD: 'ABC#80144315',

  CHECKPOINT_EVERY: 50, // guardar cada N filas procesadas
};

// ══════════════════════════════════════════════════════════════════
//  CLI args
// ══════════════════════════════════════════════════════════════════
const DRY_RUN = process.argv.includes('--dry');
const RESUME = process.argv.includes('--resume');
const argLimit = process.argv.find((a) => a.startsWith('--limit='));
const argDelay = process.argv.find((a) => a.startsWith('--delay='));
const LIMIT = argLimit ? parseInt(argLimit.split('=')[1], 10) : null;
const DELAY_MS = argDelay ? parseInt(argDelay.split('=')[1], 10) : 80;

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

function log(prefix: string, msg: string) {
  const ts = new Date().toISOString().slice(11, 19);
  const line = `[${ts}] ${prefix} ${msg}`;
  console.log(line);
  logStream.write(line + '\n');
}
const info = (m: string) => log('INFO ', m);
const ok = (m: string) => log('✓    ', m);
const warn = (m: string) => log('WARN ', m);
const err = (m: string) => log('✗ ERR', m);
const raw = (m: string) => {
  console.log(m);
  logStream.write(m + '\n');
};

// ══════════════════════════════════════════════════════════════════
//  Helpers
// ══════════════════════════════════════════════════════════════════
function sanitizarRuc(v: any): string {
  return String(v ?? '')
    .split('-')[0]
    .replace(/[\s.]/g, '')
    .trim();
}

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// ══════════════════════════════════════════════════════════════════
//  Autenticación + consulta SIFEN
// ══════════════════════════════════════════════════════════════════
let tokenAPI: string | null = null;

async function autenticarAPI(): Promise<{ ok: boolean; motivo?: string }> {
  return new Promise((resolve) => {
    const body = JSON.stringify({ ruc: CONFIG.MDWARE_RUC, password: CONFIG.MDWARE_PASSWORD });
    const url = new URL(`${CONFIG.MDWARE_BASE_URL}/api/auth`);
    const options = {
      hostname: url.hostname,
      port: url.port || 443,
      path: url.pathname,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
      },
      timeout: 10_000,
    };

    const req = httpRequest(url)(options, (res) => {
      let body = '';
      res.on('data', (c) => (body += c));
      res.on('end', () => {
        const status = res.statusCode ?? 0;
        if (status === 401 || status === 403) {
          resolve({ ok: false, motivo: `Credenciales incorrectas (HTTP ${status})` });
          return;
        }
        if (status < 200 || status >= 300) {
          resolve({ ok: false, motivo: `HTTP ${status}: ${body.substring(0, 120)}` });
          return;
        }
        try {
          const data = JSON.parse(body) as { status: string; token?: string };
          if (data?.token) {
            tokenAPI = data.token;
            resolve({ ok: true });
          } else {
            resolve({ ok: false, motivo: `Respuesta sin token: ${body.substring(0, 120)}` });
          }
        } catch {
          resolve({ ok: false, motivo: `Respuesta no es JSON: ${body.substring(0, 80)}` });
        }
      });
    });
    req.on('error', (e) => resolve({ ok: false, motivo: `Conexión: ${e.message}` }));
    req.on('timeout', () => {
      req.destroy();
      resolve({ ok: false, motivo: 'Timeout (>10s)' });
    });
    req.write(body);
    req.end();
  });
}

interface SifenResult {
  esContribuyente: boolean;
  razonSocial?: string;
  codRespuesta?: string;
  descripcion?: string;
  error?: string;
}

async function consultarSifen(nroDoc: string): Promise<SifenResult> {
  return new Promise((resolve) => {
    const body = JSON.stringify({ tipOpe: '7', ruc: nroDoc });
    const url = new URL(`${CONFIG.MDWARE_BASE_URL}/api/maintenance`);
    const options = {
      hostname: url.hostname,
      port: url.port || 443,
      path: url.pathname,
      method: 'POST',
      headers: {
        Authorization: `Bearer ${tokenAPI}`,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
      },
      timeout: 10_000,
    };

    const req = httpRequest(url)(options, (res) => {
      let rawBody = '';
      res.on('data', (c) => (rawBody += c));
      res.on('end', () => {
        try {
          const data = JSON.parse(rawBody);
          const codResp = data?.response?.cod_respuesta ?? '';
          const desc = data?.response?.d_des_resp ?? data?.response?.descripcion ?? '';
          const razon = data?.response?.razon_social ?? '';

          if (data?.status === 'success' && codResp === '0502') {
            resolve({ esContribuyente: true, razonSocial: razon, codRespuesta: codResp, descripcion: desc });
          } else {
            resolve({ esContribuyente: false, codRespuesta: codResp, descripcion: desc });
          }
        } catch {
          resolve({ esContribuyente: false, error: 'JSON inválido' });
        }
      });
    });

    req.on('error', (e: any) => resolve({ esContribuyente: false, error: e.message }));
    req.on('timeout', () => {
      req.destroy();
      resolve({ esContribuyente: false, error: 'Timeout' });
    });
    req.write(body);
    req.end();
  });
}

// ══════════════════════════════════════════════════════════════════
//  MAIN
// ══════════════════════════════════════════════════════════════════
async function run() {
  raw('');
  raw('╔══════════════════════════════════════════════════════╗');
  raw(`║  ENRIQUECER EXCEL CON RAZÓN SOCIAL  ${DRY_RUN ? '[ DRY RUN ]   ' : '[ REAL ]      '}║`);
  raw(`║  Log: ${path.basename(LOG_FILE).padEnd(46)}║`);
  raw('╚══════════════════════════════════════════════════════╝');
  raw('');

  if (!fs.existsSync(CONFIG.XLSX_IN)) {
    raw(`ERROR: no existe ${CONFIG.XLSX_IN}`);
    process.exit(1);
  }

  const wb = XLSX.readFile(CONFIG.XLSX_IN);
  const sheet = wb.Sheets[CONFIG.SHEET_NAME] ?? wb.Sheets[wb.SheetNames[0]];
  if (!sheet) {
    raw(`ERROR: no se encontró la hoja "${CONFIG.SHEET_NAME}"`);
    process.exit(1);
  }
  const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });
  info(`Filas en Excel: ${rows.length}`);

  // ── Resume: cargar enriquecimientos previos ───────────────────
  const yaProcesados = new Map<string, any>(); // ruc → fila enriquecida
  if (RESUME && fs.existsSync(CONFIG.XLSX_OUT)) {
    const wbPrev = XLSX.readFile(CONFIG.XLSX_OUT);
    const sheetPrev = wbPrev.Sheets[CONFIG.SHEET_NAME] ?? wbPrev.Sheets[wbPrev.SheetNames[0]];
    const prevRows: any[] = XLSX.utils.sheet_to_json(sheetPrev, { defval: '' });
    for (const r of prevRows) {
      const ruc = sanitizarRuc(r['RUC']);
      if (ruc && r['Estado Consulta']) yaProcesados.set(ruc, r);
    }
    info(`Resume: ${yaProcesados.size} filas ya enriquecidas en salida previa`);
  }

  // ── Autenticación ─────────────────────────────────────────────
  if (!DRY_RUN) {
    info(`Autenticando en mdware (RUC ${CONFIG.MDWARE_RUC})...`);
    const auth = await autenticarAPI();
    if (!auth.ok) {
      raw('');
      raw(`✗ FATAL: ${auth.motivo}`);
      logStream.end();
      process.exit(1);
    }
    ok('Token obtenido.');
    raw('');
  }

  // ── Iterar ────────────────────────────────────────────────────
  const total = LIMIT ? Math.min(LIMIT, rows.length) : rows.length;
  let contrib = 0;
  let noContrib = 0;
  let errores = 0;
  let sinRuc = 0;
  let reusados = 0;
  const enriched: any[] = [];

  const guardarParcial = (procesadas: number) => {
    const filasFinales = [...enriched, ...rows.slice(procesadas)];
    const wbOut = XLSX.utils.book_new();
    // Preservar hoja "Cómo usar" si existe
    for (const name of wb.SheetNames) {
      if (name !== CONFIG.SHEET_NAME && name !== wb.SheetNames[0]) {
        XLSX.utils.book_append_sheet(wbOut, wb.Sheets[name], name);
      }
    }
    const newSheet = XLSX.utils.json_to_sheet(filasFinales);
    XLSX.utils.book_append_sheet(wbOut, newSheet, CONFIG.SHEET_NAME);
    // Append "Cómo usar" al final si existía y no se agregó
    for (const name of wb.SheetNames) {
      if (name !== CONFIG.SHEET_NAME && !wbOut.SheetNames.includes(name)) {
        XLSX.utils.book_append_sheet(wbOut, wb.Sheets[name], name);
      }
    }
    XLSX.writeFile(wbOut, CONFIG.XLSX_OUT);
  };

  for (let i = 0; i < total; i++) {
    const row = rows[i];
    const ruc = sanitizarRuc(row['RUC']);
    const progreso = `[${String(i + 1).padStart(4)}/${total}]`;

    if (!ruc) {
      sinRuc++;
      enriched.push({
        ...row,
        'Razon Social': '',
        'Estado Consulta': 'SIN_RUC',
        'Cod Respuesta': '',
        Mensaje: '',
      });
      warn(`${progreso} fila sin RUC — omitida`);
      continue;
    }

    // ── Reusar si ya estaba enriquecida ─────────────────────────
    if (RESUME && yaProcesados.has(ruc)) {
      const prev = yaProcesados.get(ruc);
      enriched.push({
        ...row,
        'Razon Social': prev['Razon Social'] || '',
        'Estado Consulta': prev['Estado Consulta'] || '',
        'Cod Respuesta': prev['Cod Respuesta'] || '',
        Mensaje: prev['Mensaje'] || '',
      });
      reusados++;
      if (prev['Estado Consulta'] === 'CONTRIBUYENTE') contrib++;
      else if (prev['Estado Consulta'] === 'NO_CONTRIBUYENTE') noContrib++;
      else errores++;
      continue;
    }

    let res: SifenResult;
    if (DRY_RUN) {
      res = { esContribuyente: true, razonSocial: `[DRY] Razón ${ruc}`, codRespuesta: '0502' };
    } else {
      res = await consultarSifen(ruc);
      if (DELAY_MS > 0) await sleep(DELAY_MS);
    }

    if (res.error) {
      errores++;
      enriched.push({
        ...row,
        'Razon Social': '',
        'Estado Consulta': 'ERROR',
        'Cod Respuesta': '',
        Mensaje: res.error,
      });
      err(`${progreso} RUC=${ruc} ERROR: ${res.error}`);
    } else if (res.esContribuyente) {
      contrib++;
      enriched.push({
        ...row,
        'Razon Social': res.razonSocial || '',
        'Estado Consulta': 'CONTRIBUYENTE',
        'Cod Respuesta': res.codRespuesta || '',
        Mensaje: res.descripcion || '',
      });
      ok(`${progreso} RUC=${ruc} → ${(res.razonSocial || '').substring(0, 50)}`);
    } else {
      noContrib++;
      enriched.push({
        ...row,
        'Razon Social': '',
        'Estado Consulta': 'NO_CONTRIBUYENTE',
        'Cod Respuesta': res.codRespuesta || '',
        Mensaje: res.descripcion || '',
      });
      info(`${progreso} RUC=${ruc} no contribuyente (cod=${res.codRespuesta || '—'})`);
    }

    // ── Checkpoint ────────────────────────────────────────────
    if ((i + 1) % CONFIG.CHECKPOINT_EVERY === 0) {
      guardarParcial(i + 1);
      info(`  ⚑ checkpoint guardado tras ${i + 1} filas`);
    }
  }

  // ── Save final ────────────────────────────────────────────────
  guardarParcial(total);

  // ══════════════════════════════════════════════════════════════
  raw('');
  raw('╔══════════════════════════════════════════════════════╗');
  raw('║                    RESUMEN FINAL                     ║');
  raw('╠══════════════════════════════════════════════════════╣');
  raw(`║  Total procesado            : ${String(total).padStart(6)}                ║`);
  raw(`║  Contribuyentes (con razón) : ${String(contrib).padStart(6)}                ║`);
  raw(`║  No contribuyentes          : ${String(noContrib).padStart(6)}                ║`);
  raw(`║  Errores de consulta        : ${String(errores).padStart(6)}                ║`);
  raw(`║  Sin RUC                    : ${String(sinRuc).padStart(6)}                ║`);
  if (RESUME) raw(`║  Reusados (resume)          : ${String(reusados).padStart(6)}                ║`);
  raw('╚══════════════════════════════════════════════════════╝');
  raw('');
  raw(`Salida: ${CONFIG.XLSX_OUT}`);
  raw(`Log:    ${LOG_FILE}`);
  raw('');

  logStream.end();
}

run().catch((e) => {
  err(`Fatal: ${e.message}\n${e.stack}`);
  logStream.end();
  process.exit(1);
});
