// EL BUZON DE CARPETA: la via de intercambio para administrativos sin API.
//
// Muchos sistemas administrativos venezolanos no saben hacer un POST, pero
// TODOS saben escribir un archivo. El tecnico del negocio apunta su sistema a
// la carpeta intercambio/entrada y la Estacion la vigila: cada archivo .json
// con una venta (el MISMO contrato de GUIA_INTEGRACION.md, la VentaEntrante)
// se sella igual que si hubiera llegado por /api/venta, y el resultado queda
// en intercambio/respuestas con el mismo nombre.
//
// Reglas del buzon:
//  - Un archivo recien tocado NO se procesa (se le da un respiro): asi no se
//    lee un archivo a medio escribir. El que escribe puede ademas escribir con
//    otro nombre y renombrar a .json al final, que es lo mas seguro.
//  - Un archivo procesado se MUEVE (a procesadas o a rechazadas), nunca se
//    borra: carpeta de entrada vacia = todo al dia, y el rastro completo queda.
//  - Reintentar o duplicar es seguro: la idempotencia por origen_id vive en el
//    libro, asi que un archivo repetido devuelve el documento ya sellado.
//  - Si la Estacion no puede sellar ahorita (sin configurar, reloj corrido),
//    los archivos ESPERAN en entrada y se reintentan solos en la proxima ronda.
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import type { EstacionFiscal, ResultadoVenta } from './estacion.js';
import type { VentaEntrante } from './tipos.js';

export interface CarpetasIntercambio {
  entrada: string;
  procesadas: string;
  rechazadas: string;
  respuestas: string;
}

export function carpetasIntercambio(base: string): CarpetasIntercambio {
  const raiz = join(base, 'intercambio');
  return {
    entrada: join(raiz, 'entrada'),
    procesadas: join(raiz, 'procesadas'),
    rechazadas: join(raiz, 'rechazadas'),
    respuestas: join(raiz, 'respuestas'),
  };
}

/** Crea las carpetas si faltan. Se llama al arrancar, aun sin configurar: que el tecnico las VEA. */
export function prepararCarpetas(base: string): CarpetasIntercambio {
  const c = carpetasIntercambio(base);
  for (const ruta of Object.values(c)) mkdirSync(ruta, { recursive: true });
  return c;
}

/** Lo minimo que debe traer el archivo para intentar sellarlo. Lo demas lo valida la Estacion. */
function defectoDeForma(v: unknown): string | null {
  if (!v || typeof v !== 'object') return 'El archivo no trae una venta (se esperaba un objeto JSON)';
  const venta = v as Partial<VentaEntrante>;
  if (typeof venta.origen_id !== 'string' || !venta.origen_id.trim()) {
    return 'Falta origen_id (el identificador de la venta en tu sistema)';
  }
  if (!Array.isArray(venta.renglones) || venta.renglones.length === 0) {
    return 'Falta renglones (que se vendio): revisa la GUIA_INTEGRACION.md';
  }
  return null;
}

/** Mueve sin pisar: si ya existe uno con ese nombre, le agrega un sufijo. */
function mover(desde: string, hacia: string, nombre: string): void {
  let destino = join(hacia, nombre);
  if (existsSync(destino)) destino = join(hacia, `${Date.now()}-${nombre}`);
  renameSync(desde, destino);
}

export interface ResultadoRondaCarpeta {
  selladas: number;
  rechazadas: number;
  /** Quedaron esperando (recien escritas o la Estacion no puede sellar ahorita). */
  esperando: number;
}

/**
 * Una ronda del buzon: procesa lo que haya en entrada.
 * @param respiroMs cuanto debe llevar quieto un archivo antes de tocarlo.
 */
export async function procesarCarpeta(
  est: EstacionFiscal,
  base: string,
  respiroMs = 1500,
): Promise<ResultadoRondaCarpeta> {
  const c = prepararCarpetas(base);
  const r: ResultadoRondaCarpeta = { selladas: 0, rechazadas: 0, esperando: 0 };

  const archivos = readdirSync(c.entrada)
    .filter((n) => n.toLowerCase().endsWith('.json'))
    .sort();

  for (const nombre of archivos) {
    const ruta = join(c.entrada, nombre);

    // El respiro: si lo acaban de escribir, se deja quieto hasta la proxima.
    let quietoDesde: number;
    try {
      quietoDesde = statSync(ruta).mtimeMs;
    } catch {
      continue; // se lo llevaron entre el listado y aqui
    }
    if (Date.now() - quietoDesde < respiroMs) {
      r.esperando++;
      continue;
    }

    const responder = (resultado: ResultadoVenta): void => {
      writeFileSync(join(c.respuestas, `${nombre}.respuesta.json`), JSON.stringify(resultado, null, 2));
    };

    let venta: unknown;
    try {
      venta = JSON.parse(readFileSync(ruta, 'utf8'));
    } catch {
      responder({ sellada: false, problema: 'El archivo no es JSON valido' });
      mover(ruta, c.rechazadas, nombre);
      r.rechazadas++;
      continue;
    }

    const defecto = defectoDeForma(venta);
    if (defecto) {
      responder({ sellada: false, problema: defecto });
      mover(ruta, c.rechazadas, nombre);
      r.rechazadas++;
      continue;
    }

    try {
      const resultado = await est.recibirVenta(venta as VentaEntrante, 'carpeta');
      responder(resultado);
      if (resultado.sellada) {
        mover(ruta, c.procesadas, nombre);
        r.selladas++;
      } else {
        // No se pudo sellar AHORITA (ej. reloj corrido): el archivo espera y se
        // reintenta solo. La respuesta ya dice por que, para quien mire.
        r.esperando++;
      }
    } catch (e) {
      // La venta tiene algo que la Estacion no acepta: eso no se arregla
      // reintentando, va a rechazadas con su porque.
      responder({ sellada: false, problema: (e as Error).message });
      mover(ruta, c.rechazadas, nombre);
      r.rechazadas++;
    }
  }
  return r;
}
