import fs from 'fs';
import { USERS_FILE, ORDERS_FILE, CREATORS_FILE, WITHDRAWALS_FILE } from './config.js';

// Inicializa los archivos JSON si no existen (usuarios, órdenes, creadores y retiros)
if (!fs.existsSync(USERS_FILE)) fs.writeFileSync(USERS_FILE, JSON.stringify({}, null, 2));
if (!fs.existsSync(ORDERS_FILE)) fs.writeFileSync(ORDERS_FILE, JSON.stringify([], null, 2));
if (!fs.existsSync(CREATORS_FILE)) fs.writeFileSync(CREATORS_FILE, JSON.stringify({}, null, 2));
if (!fs.existsSync(WITHDRAWALS_FILE)) fs.writeFileSync(WITHDRAWALS_FILE, JSON.stringify([], null, 2));

// ─── Mutex async en memoria (cola FIFO de promesas) ───────────────────────
// Garantiza que readDB+writeDB no se pisen entre requests concurrentes.
// Antes: dos POST /api/orders simultáneos leían el mismo orders=[], cada uno
// agregaba su orden, y el último writeDB ganaba → se perdía una orden.
// El mutex serializa la sección crítica (todos los accesos pasan por withDB).
let __chain = Promise.resolve();
function __withLock(critical) {
  // Encadena `critical` al final de la cadena de promesas. Cada nuevo llamadaor
  // espera a que terminen todos los anteriores antes de ejecutarse.
  const run = __chain.then(critical, critical);
  // El `.catch` evita que un rechazo rompa la cadena para siempre.
  __chain = run.then(() => {}, () => {});
  return run;
}

// ─── Helpers de escritura atómica ────────────────────────────────────────
// Escribe a <file>.tmp y luego rename -> <file>. En NTFS, rename sobre el
// mismo volumen es atómico: el archivo destino nunca queda truncado/parcial
// aunque el proceso muera a mitad de write. Esto protege users.json y orders.json
// contra cortes de luz / crash intermedio.
// En Windows, rename puede fallar con EPERM si el archivo está bloqueado;
// reintentamos con backoff exponencial.
async function __atomicWriteJSON(file, data) {
  const tmp = file + '.tmp';
  fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
  
  // Retry rename en Windows (EPERM = file locked by AV/backup/otro proceso)
  for (let attempt = 0; attempt < 10; attempt++) {
    try {
      fs.renameSync(tmp, file);
      return;
    } catch (e) {
      if (e.code !== 'EPERM' || attempt === 9) throw e;
      await new Promise(r => setTimeout(r, 10 * (attempt + 1)));
    }
  }
}

// readDB/writeDB mantienen la forma { users, orders } para no tocar rutas ni
// servicios, pero cada parte se persiste en su propio archivo.
// ⚠️ Preferí `withDB` para cualquier mutación — ver abajo.
export const readDB = () => ({
  users: JSON.parse(fs.readFileSync(USERS_FILE, 'utf-8')),
  orders: JSON.parse(fs.readFileSync(ORDERS_FILE, 'utf-8')),
  creators: JSON.parse(fs.readFileSync(CREATORS_FILE, 'utf-8')),
  withdrawals: JSON.parse(fs.readFileSync(WITHDRAWALS_FILE, 'utf-8')),
});

export async function writeDB(data) {
  await Promise.all([
    __atomicWriteJSON(USERS_FILE, data.users ?? {}),
    __atomicWriteJSON(ORDERS_FILE, data.orders ?? []),
    __atomicWriteJSON(CREATORS_FILE, data.creators ?? {}),
    __atomicWriteJSON(WITHDRAWALS_FILE, data.withdrawals ?? []),
  ]);
}

/**
 * withDB(mutator): sección crítica read → mutate → write atómica.
 *
 * `mutator(db)` recibe una COPIA en memoria de { users, orders } y debe
 * devolver el nuevo estado (o mutar la copia y devolverla). El resultado
 * se persiste con writeDB atómico, todo bajo el mutex async.
 *
 * El callback puede ser async (devolver Promise) — el lock se mantiene
 * hasta que se resuelva, pero EVITÁ hacer fetch/IO externo dentro del
 * callback porque bloquea toda la cola de escritores.
 *
 * Uso típico:
 *   const newOrder = await withDB(db => {
 *     db.orders.push(order);
 *     return db;
 *   });
 *
 * `options.snapshot=true` devuelve el snapshot leído antes de mutar
 * (útil para rutas que solo necesitan leer consistente + mutar).
 */
export async function withDB(mutator) {
  return __withLock(async () => {
    const snapshot = readDB();
    const result = await mutator(snapshot);
    // Mutamos in-place el snapshot. Ya no usamos el valor de retorno como
    // nuevo estado para no pisar la DB si el mutator devuelve un objeto HTTP
    // o resultados custom. Siempre persistimos el snapshot mutado.
    await writeDB(snapshot);
    return result;
  });
}

/**
 * readDBOnce: alias legible para "leer bajo lock" cuando una ruta solo
 * necesita leer sin mutar. Útil para evitar que un read caiga en medio
 * de un writeDB parcial en otra corrutina.
 *
 * Nota: como Node es single-thread y readDB/writeDB son sincrónicos,
 * usar readDB() directo es seguro SIEMPRE que el llamador no haya
 * await'ado nada entre read y write. Si hay un await en el medio,
 * usar conDB/readDBOnce para garantía. */
export async function readDBOnce() {
  return __withLock(() => readDB());
}
