'use strict';

const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

const ROOT = path.join(__dirname, '..', '..');
const UPDATE_DIR = path.join(ROOT, '.updates');
const BACKUP_DIR = path.join(UPDATE_DIR, 'backups');
const PENDING_FILE = path.join(UPDATE_DIR, 'pending-update.json');
const APPLIED_FILE = path.join(UPDATE_DIR, 'last-applied.json');

const CODE_DIRS = ['src', 'frontend', 'config', 'installer', 'scripts'];
const CODE_FILES = ['db.js', 'index.js', 'setup.js', 'setup-db.js', 'instalar.js', 'package.json', 'package-lock.json'];

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
}

function readJson(file, fallback = null) {
  try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) { return fallback; }
}

function writeJson(file, payload) {
  ensureDir(path.dirname(file));
  fs.writeFileSync(file, JSON.stringify(payload, null, 2), 'utf8');
}

function copyDir(src, dest) {
  if (!fs.existsSync(src)) return;
  ensureDir(dest);
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    const sourcePath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isDirectory()) copyDir(sourcePath, destPath);
    else fs.copyFileSync(sourcePath, destPath);
  }
}

function removeIfExists(target) {
  if (fs.existsSync(target)) fs.rmSync(target, { recursive: true, force: true });
}

function currentVersion() {
  return readJson(path.join(ROOT, 'package.json'), { version: '0.0.0' }).version || '0.0.0';
}

function createCodeBackup(meta = {}) {
  ensureDir(BACKUP_DIR);
  const stamp = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
  const backupPath = path.join(BACKUP_DIR, `${stamp}-${currentVersion()}`);
  ensureDir(backupPath);

  for (const dir of CODE_DIRS) {
    const src = path.join(ROOT, dir);
    if (fs.existsSync(src)) copyDir(src, path.join(backupPath, dir));
  }
  for (const file of CODE_FILES) {
    const src = path.join(ROOT, file);
    if (fs.existsSync(src)) fs.copyFileSync(src, path.join(backupPath, file));
  }

  writeJson(path.join(backupPath, 'backup-manifest.json'), {
    created_at: new Date().toISOString(),
    version: currentVersion(),
    ...meta,
  });
  return backupPath;
}

function restoreBackup(backupPath) {
  if (!backupPath || !fs.existsSync(backupPath)) {
    throw new Error('Backup de rollback no encontrado.');
  }

  for (const dir of CODE_DIRS) {
    const src = path.join(backupPath, dir);
    const dest = path.join(ROOT, dir);
    if (fs.existsSync(src)) {
      removeIfExists(dest);
      copyDir(src, dest);
    }
  }

  for (const file of CODE_FILES) {
    const src = path.join(backupPath, file);
    if (fs.existsSync(src)) fs.copyFileSync(src, path.join(ROOT, file));
  }
}

function markPendingUpdate(payload) {
  writeJson(PENDING_FILE, {
    attempts: 0,
    created_at: new Date().toISOString(),
    ...payload,
  });
}

function clearPendingUpdate(status = 'applied') {
  const pending = readJson(PENDING_FILE, null);
  if (pending) {
    writeJson(APPLIED_FILE, {
      ...pending,
      status,
      completed_at: new Date().toISOString(),
      installed_version: currentVersion(),
    });
  }
  removeIfExists(PENDING_FILE);
}

function restartDetached(delayMs = 1000) {
  setTimeout(() => {
    const nodeExe = process.execPath;
    const indexJs = path.join(ROOT, 'index.js');
    spawn(nodeExe, [indexJs], {
      cwd: ROOT,
      detached: true,
      stdio: 'ignore',
      env: process.env,
      windowsHide: true,
    }).unref();
    setTimeout(() => process.exit(0), 500);
  }, delayMs);
}

function schedulePendingValidation(logger, options = {}) {
  const pending = readJson(PENDING_FILE, null);
  if (!pending) return;

  const delayMs = options.delayMs || 8000;
  setTimeout(() => {
    try {
      const latest = readJson(PENDING_FILE, null);
      if (!latest) return;

      latest.attempts = Number(latest.attempts || 0) + 1;
      latest.last_validation_at = new Date().toISOString();
      writeJson(PENDING_FILE, latest);

      const installed = currentVersion();
      const expected = String(latest.target_version || '').trim();
      const healthOk = fs.existsSync(path.join(ROOT, 'index.js')) &&
        fs.existsSync(path.join(ROOT, 'src')) &&
        fs.existsSync(path.join(ROOT, 'frontend'));

      if (healthOk && (!expected || installed === expected)) {
        logger?.info?.({ event: 'update_validated', version: installed });
        clearPendingUpdate('applied');
        return;
      }

      if (latest.attempts < 2) {
        logger?.warn?.({ event: 'update_validation_retry', installed, expected, attempts: latest.attempts });
        return;
      }

      logger?.error?.({ event: 'update_rollback_start', installed, expected, backup: latest.backup_dir });
      restoreBackup(latest.backup_dir);
      clearPendingUpdate('rolled_back');
      restartDetached(1000);
    } catch (err) {
      logger?.error?.({ event: 'update_validation_error', error: err.message });
      try {
        const latest = readJson(PENDING_FILE, null);
        if (latest?.backup_dir) {
          restoreBackup(latest.backup_dir);
          clearPendingUpdate('rolled_back_after_error');
          restartDetached(1000);
        }
      } catch (rollbackErr) {
        logger?.error?.({ event: 'update_rollback_failed', error: rollbackErr.message });
      }
    }
  }, delayMs);
}

module.exports = {
  ROOT,
  CODE_DIRS,
  CODE_FILES,
  createCodeBackup,
  restoreBackup,
  markPendingUpdate,
  clearPendingUpdate,
  schedulePendingValidation,
  restartDetached,
};
