// PRUEBA DEL INSTALADOR (2026-07-31)
// Se ejercita el EJECUTABLE COMPILADO, no una copia de su logica: se levanta
// un canal de mentira y se comprueba lo que de verdad importa en la laptop de
// un negocio con mal internet.
import { execFile } from 'node:child_process';
import { createServer } from 'node:http';
import { existsSync, mkdtempSync, readdirSync, rmSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';

const correr = promisify(execFile);
const AQUI = dirname(fileURLToPath(import.meta.url));
const EXE = join(AQUI, '..', 'dist-exe', 'Instalar-Estacion-Fiscal.exe');
const EXE_CEREBRITO = join(AQUI, '..', 'dist-exe', 'Instalar-Cerebrito.exe');

let fallos = 0;
const check = (cond, msg) => {
  console.log((cond ? '  OK  ' : '  XX  ') + msg);
  if (!cond) fallos++;
};

const dir = mkdtempSync(join(tmpdir(), 'gx-inst-'));
const PUERTO = 8753;
// Un "programa" de mentira que pesa lo suficiente para pasar el minimo.
const PROGRAMA = Buffer.alloc(11_000_000, 7);
let cortar = false;

const canal = createServer((req, res) => {
  if (req.url.includes('version.json')) {
    res.writeHead(200, { 'content-type': 'application/json' });
    return res.end(JSON.stringify({ version: '9.9.9', url: `http://127.0.0.1:${PUERTO}/programa.exe` }));
  }
  if (req.url.includes('programa.exe')) {
    // Se anuncia el tamaño completo y se manda menos: eso es el internet del
    // local cortandose a mitad, y NO puede quedar instalado.
    res.writeHead(200, { 'content-type': 'application/octet-stream', 'content-length': String(PROGRAMA.length) });
    res.write(cortar ? PROGRAMA.subarray(0, PROGRAMA.length - 5000) : PROGRAMA);
    return res.end();
  }
  res.writeHead(404);
  res.end();
});
await new Promise((listo) => canal.listen(PUERTO, '127.0.0.1', listo));

const entorno = { ...process.env, GX_CANAL: `http://127.0.0.1:${PUERTO}`, GX_DESTINO: dir };
const instalar = async () => {
  try {
    // Un programa de ventana (winexe) no tiene consola propia: su salida
    // redirigida viene en la pagina ANSI de Windows, no en UTF-8.
    const { stdout } = await correr(EXE, ['--consola'], { env: entorno, timeout: 120000, encoding: 'latin1' });
    return { codigo: 0, salida: stdout.trim() };
  } catch (e) {
    return { codigo: e.code ?? 1, salida: Buffer.from(e.stdout ?? '', 'binary').toString('latin1').trim() };
  }
};
const rutaEsperada = join(dir, 'EstacionFiscal', 'Estacion-Fiscal.exe');

try {
  console.log('\n== 1. El instalador pesa lo que debe pesar ==');
  check(existsSync(EXE) && existsSync(EXE_CEREBRITO), 'los dos instaladores existen');
  const kb = Math.round(statSync(EXE).size / 1024);
  check(kb < 500, `el instalador pesa ${kb} KB (no los 90 MB del programa que trae)`);

  console.log('\n== 2. Lee el canal y dice que version va a instalar ==');
  cortar = true;
  const cortada = await instalar();
  check(/VERSION 9\.9\.9/.test(cortada.salida), 'consulta el canal y anuncia la version publicada');

  console.log('\n== 3. Una descarga cortada NO queda instalada ==');
  check(cortada.codigo === 1, `sale con codigo de problema (${cortada.codigo})`);
  check(/incompleta/i.test(cortada.salida), 'avisa que la descarga quedo incompleta');
  check(/conexión a internet/i.test(cortada.salida), 'y le dice al negocio que revise su conexion');
  check(!existsSync(rutaEsperada), 'NO deja un programa a medias haciendose pasar por el bueno');
  const sobras = existsSync(join(dir, 'EstacionFiscal'))
    ? readdirSync(join(dir, 'EstacionFiscal')).filter((f) => f.endsWith('.bajando'))
    : [];
  check(sobras.length === 0, 'y el archivo a medio bajar se limpia');

  console.log('\n== 4. La descarga completa si se instala ==');
  cortar = false;
  const buena = await instalar();
  check(buena.codigo === 0, `termina bien (codigo ${buena.codigo})`);
  check(existsSync(rutaEsperada), 'el programa quedo en la carpeta de su producto');
  check(statSync(rutaEsperada).size === PROGRAMA.length, 'con el tamaño completo, byte a byte');
  check(/INSTALADO/.test(buena.salida), 'y lo dice');

  console.log('\n== 5. Cada instalador trae UN producto, y no menciona al otro ==');
  const crudoEstacion = statSync(EXE).size;
  const crudoCerebrito = statSync(EXE_CEREBRITO).size;
  check(crudoEstacion > 0 && crudoCerebrito > 0, 'los dos se compilaron por separado');
  // Las cadenas de un ejecutable .NET van en UTF-16: leerlo como texto de un
  // byte no encontraria nada y el chequeo pasaria por la razon equivocada.
  const { readFileSync } = await import('node:fs');
  const textoEstacion = readFileSync(EXE).toString('utf16le');
  check(/Estación Fiscal/.test(textoEstacion), 'el de la Estacion lleva su nombre adentro');
  check(!/Cerebrito/.test(textoEstacion), 'y NO menciona al Cerebrito: la frontera se respeta');
  const textoCerebrito = readFileSync(EXE_CEREBRITO).toString('utf16le');
  check(/Cerebrito/.test(textoCerebrito), 'el del Cerebrito lleva el suyo');
  check(!/Estación Fiscal/.test(textoCerebrito), 'y tampoco mezcla al reves');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  canal.close();
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nINSTALADOR: TODO VERDE' : `\n${fallos} FALLO(S)`);
process.exit(fallos === 0 ? 0 : 1);
