// Logica condivisa fra POST /api/admin/restore (upload) e
// POST /api/admin/factory-reset (file pre-caricato).
// Estrae uno zip in staging, valida la struttura, e fa lo swap atomico di data/.

import { spawn } from 'node:child_process';
import { mkdir, readFile, rename, rm } from 'node:fs/promises';
import path from 'node:path';
import crypto from 'node:crypto';

const PROJECT_ROOT = process.cwd();
const DATA_DIR = path.join(PROJECT_ROOT, 'data');
const RESTORE_STAGING = path.join(PROJECT_ROOT, '.restore-staging');

export type RestoreResult =
  | { ok: true; cards: number; portals: number; previousBackup: string }
  | { ok: false; status: number; error: string; detail?: string };

function runUnzip(zipPath: string, destDir: string): Promise<{ code: number; stderr: string }> {
  return new Promise(resolve => {
    const child = spawn('unzip', ['-o', '-q', zipPath, '-d', destDir]);
    let stderr = '';
    child.stderr.on('data', d => { stderr += d.toString(); });
    child.on('error', () => resolve({ code: -1, stderr: stderr || 'unzip non avviato' }));
    child.on('exit', code => resolve({ code: code ?? -1, stderr }));
  });
}

/**
 * Estrae lo zip dal percorso dato, valida cards.txt + portals.txt come JSON array,
 * poi sostituisce atomicamente DATA_DIR. La vecchia data/ viene rinominata
 * data.bak-<ts>/ come safety net (mai cancellata automaticamente).
 */
export async function restoreFromZipFile(zipPath: string): Promise<RestoreResult> {
  const sessionDir = path.join(RESTORE_STAGING, crypto.randomUUID());
  const extractDir = path.join(sessionDir, 'extract');
  await mkdir(extractDir, { recursive: true });

  try {
    const { code, stderr } = await runUnzip(zipPath, extractDir);
    if (code !== 0) {
      return {
        ok: false,
        status: 400,
        error: 'Impossibile estrarre lo ZIP.',
        detail: stderr.split('\n').slice(0, 3).join(' '),
      };
    }

    let cards = 0;
    let portals = 0;
    try {
      const raw = await readFile(path.join(extractDir, 'cards.txt'), 'utf-8');
      const parsed = JSON.parse(raw || '[]');
      if (!Array.isArray(parsed)) throw new Error('cards.txt non è un array JSON');
      cards = parsed.length;
    } catch (e) {
      return { ok: false, status: 400, error: `Backup non valido: cards.txt assente o malformato (${(e as Error).message}).` };
    }
    try {
      const raw = await readFile(path.join(extractDir, 'portals.txt'), 'utf-8');
      const parsed = JSON.parse(raw || '[]');
      if (!Array.isArray(parsed)) throw new Error('portals.txt non è un array JSON');
      portals = parsed.length;
    } catch (e) {
      return { ok: false, status: 400, error: `Backup non valido: portals.txt assente o malformato (${(e as Error).message}).` };
    }

    // Atomic swap: data → data.bak-<ts>, extract → data.
    const ts = Date.now();
    const backupOldDir = path.join(PROJECT_ROOT, `data.bak-${ts}`);
    try {
      await rename(DATA_DIR, backupOldDir);
    } catch (err) {
      const e = err as NodeJS.ErrnoException;
      if (e.code !== 'ENOENT') throw err;
    }
    try {
      await rename(extractDir, DATA_DIR);
    } catch (err) {
      try { await rename(backupOldDir, DATA_DIR); } catch { /* ignore */ }
      throw err;
    }

    return { ok: true, cards, portals, previousBackup: path.basename(backupOldDir) };
  } finally {
    try { await rm(sessionDir, { recursive: true, force: true }); } catch { /* ignore */ }
  }
}
