// ARRANQUE DE LA ESTACION FISCAL.
//
// Se abre en la laptop del negocio y queda escuchando. Una instalacion, un RIF.
// Guarda todo junto al programa (libro, bitacora, cola, respaldo), asi el
// negocio puede llevarse la carpeta completa si cambia de equipo.
import { dirname } from 'node:path';
import { asegurarArranque } from './arranque.js';
import { iniciarServidor } from './servidor.js';
import { EstacionFiscal } from './estacion.js';
import { leerConfig, puedeOperar } from './config.js';
import { prepararCarpetas, procesarCarpeta } from './intercambio.js';

// ¿Somos el programa instalado (.exe)? Decide donde vive todo Y si se puede
// tocar el registro de Windows (el desarrollo y las pruebas JAMAS lo tocan).
const ES_EXE = /Estacion-?Fiscal/i.test(process.execPath) || process.env.FISCAL_EMPAQUETADO === '1';

/** Donde vive todo: junto al ejecutable, o la carpeta actual en desarrollo. */
function carpetaBase(): string {
  return ES_EXE ? dirname(process.execPath) : process.cwd();
}

const registro: string[] = [];
function log(m: string): void {
  const linea = `${new Date().toLocaleTimeString('es-VE')} ${m}`;
  registro.push(linea);
  if (registro.length > 200) registro.shift();
  console.log(linea);
}

async function arrancar(): Promise<void> {
  const base = process.env.FISCAL_BASE ?? carpetaBase();
  log(`Estacion Fiscal, carpeta ${base}`);

  const cfg = leerConfig(base);
  if (!puedeOperar(cfg)) {
    log('Todavia falta configurar la estacion: abre la direccion de abajo y llena los datos del negocio.');
  } else {
    // Al arrancar se empuja lo que quedo sin transmitir de la corrida anterior.
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await est.reintentar();
      if (r.enviados > 0 || r.pendientes > 0) {
        log(`Al arrancar: ${r.enviados} documento(s) transmitido(s), ${r.pendientes} pendiente(s).`);
      }
    } catch (e) {
      log(`No se pudo abrir el libro: ${(e as Error).message}`);
    }
  }

  // El puerto se puede mover para probar dos estaciones en el mismo equipo sin
  // que se pisen. En el negocio nadie lo toca: queda en el de siempre.
  const puerto = Number(process.env.FISCAL_PUERTO) || undefined;
  const servidor = await iniciarServidor({ base, log, puerto, esExe: ES_EXE });

  // Icono en el escritorio y arranque con Windows: una estacion fiscal no
  // puede depender de que alguien se acuerde de abrirla. Solo el .exe.
  await asegurarArranque({ base, esExe: ES_EXE, puerto: servidor.puerto, log });

  // Reintento periodico: el internet del local va y viene, y la norma pide
  // remision continua. Cada minuto se empuja lo que este esperando.
  setInterval(async () => {
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await est.reintentar();
      if (r.enviados > 0) log(`Transmitidos ${r.enviados} documento(s) que estaban esperando.`);
    } catch {
      /* la estacion todavia no esta configurada */
    }
  }, 60_000);

  // EL RELOJ. Se compara al arrancar y cada quince minutos: si alguien le mueve
  // la hora a Windows a media tarde, la estacion se entera sola y deja de
  // sellar hasta que se corrija.
  const revisarReloj = async (): Promise<void> => {
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await est.revisarReloj();
      if (!r.bien) log(`Atencion: la hora del equipo esta ${r.texto}. No se van a sellar documentos hasta corregirla.`);
    } catch {
      /* todavia sin configurar */
    }
  };
  setTimeout(() => void revisarReloj(), 2_000);
  setInterval(() => void revisarReloj(), 15 * 60_000);

  // EL RESPALDO SALE DEL EQUIPO. Cada diez minutos se le manda a la imprenta lo
  // que le falte, ya cifrado. Si se quema esta laptop, el libro no se quema con
  // ella. Solo sale lo que no haya salido: primero se pregunta hasta donde tiene
  // ella guardado.
  const mandarRespaldo = async (): Promise<void> => {
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await est.subirRespaldo();
      if (r.subidas > 0) log(`Respaldo: salieron ${r.subidas} asiento(s) del equipo.`);
    } catch {
      /* todavia sin configurar */
    }
  };
  setTimeout(() => void mandarRespaldo(), 10_000);
  setInterval(() => void mandarRespaldo(), 10 * 60_000);

  // EL BUZON DE CARPETA: la via para administrativos que no saben hacer un POST
  // pero si escribir un archivo (la mayoria del parque criollo). Las carpetas se
  // crean desde ya, aun sin configurar, para que el tecnico las vea y apunte su
  // sistema; los archivos esperan solos hasta que la estacion pueda sellar.
  prepararCarpetas(base);
  setInterval(async () => {
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await procesarCarpeta(est, base);
      if (r.selladas > 0) log(`Buzon de carpeta: ${r.selladas} venta(s) sellada(s).`);
      if (r.rechazadas > 0) log(`Buzon de carpeta: ${r.rechazadas} archivo(s) rechazado(s), revisa intercambio/rechazadas.`);
    } catch {
      /* todavia sin configurar: los archivos esperan en entrada */
    }
  }, 5_000);

  // RONDA CONTRA GUSTITO: hala las ventas cerradas que faltan por sellar, para
  // que el negocio no tenga que tocar nada (cobra en Gustito y el documento
  // sale solo).
  //
  // No es un reloj fijo: cada ronda pregunta cuando volver, y eso lo decide la
  // nube segun el movimiento del negocio (en hora pico cada 10 segundos, un
  // local cerrado de madrugada cada 5 minutos). Con cien negocios conectados,
  // preguntar a ciegas cada 20 segundos ahogaria la base sin necesidad.
  //
  // Si el negocio configuro un ritmo propio, ese manda: hay locales con
  // internet malo donde conviene ir mas suave.
  const ritmoFijo = Number(cfg?.nube?.cada_segundos ?? 0);
  let esperaSegundos = ritmoFijo > 0 ? Math.max(10, ritmoFijo) : 20;

  async function rondaNube(): Promise<void> {
    try {
      const est = await EstacionFiscal.abrir(base);
      const r = await est.sincronizarConNube();
      if (r.sellada > 0) log(`Selladas ${r.sellada} venta(s) que llegaron de Gustito.`);
      if (r.fallada > 0) log(`Atencion: ${r.fallada} venta(s) de Gustito no se pudieron sellar.`);
      if (r.anulada > 0) log(`Emitidas ${r.anulada} nota(s) de credito solicitadas desde el portal.`);
      if (r.anulacionFallada > 0) log(`Atencion: ${r.anulacionFallada} solicitud(es) de anulacion no se pudieron atender.`);
      if (r.problema) log(`Sin enlace con Gustito: ${r.problema}`);
      if (ritmoFijo <= 0) esperaSegundos = Math.min(600, Math.max(5, r.volverEnSegundos));
    } catch {
      // Todavia sin configurar, o sin internet: se espacia para no insistir en
      // vano, pero nunca se deja de intentar.
      if (ritmoFijo <= 0) esperaSegundos = Math.min(300, Math.max(30, esperaSegundos * 2));
    }
    setTimeout(() => void rondaNube(), esperaSegundos * 1000);
  }
  setTimeout(() => void rondaNube(), 3_000);

  // Que un error suelto no tumbe la estacion en pleno servicio.
  process.on('uncaughtException', (e) => log(`Error no atrapado: ${e.message}`));
  process.on('unhandledRejection', (e) => log(`Promesa fallida: ${String(e)}`));
}

arrancar().catch((e) => {
  console.error('No se pudo arrancar la Estacion Fiscal:', e);
  process.exit(1);
});
