/**
 * jobs/backup.js
 *
 * Daily backup of the SQLite database + supporting data files.
 * Spawns `scripts/backup-data.js` as a child process so the live
 * app keeps running (the script opens its own better-sqlite3 handle
 * with `readonly: true`, runs `.backup()` for a consistent copy,
 * then closes).
 *
 * Schedule: 02:30 daily — 30 min after the daily key-counter reset
 * so a backup never races a counters UPDATE.
 *
 * Retention: keep last 7 daily backups. Older ones get pruned so
 * the `backups/` dir doesn't fill the disk.
 *
 * The /health endpoint reports whether the last backup tick
 * succeeded (see global.__ncii_workers.backup).
 */

'use strict';

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

const ROOT      = path.resolve(__dirname, '..');
const SCRIPT    = path.join(ROOT, 'scripts', 'backup-data.js');
const BACKUP_DIR = process.env.BACKUP_DIR || path.join(ROOT, 'backups');
const KEEP_DAILY = 7;
const SCHEDULE = '30 2 * * *';

let timer = null;

function runOnce() {
  return new Promise((resolve) => {
    if (!fs.existsSync(SCRIPT)) {
      console.warn('[backup] script missing:', SCRIPT);
      return resolve({ ok: false, error: 'script_missing' });
    }
    const child = spawn(process.execPath, [SCRIPT], {
      cwd: ROOT,
      env: process.env,
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    let out = '', err = '';
    child.stdout.on('data', d => out += d.toString());
    child.stderr.on('data', d => err += d.toString());
    child.on('close', (code) => {
      if (code === 0) {
        if (global.__ncii_workers) global.__ncii_workers.backup = 'running';
        console.log('[backup] ok');
        pruneOld();
        resolve({ ok: true, output: out });
      } else {
        if (global.__ncii_workers) global.__ncii_workers.backup = 'failed';
        console.warn('[backup] failed code=' + code + ': ' + (err || out).slice(0, 500));
        resolve({ ok: false, code, error: err || out });
      }
    });
  });
}

function pruneOld() {
  try {
    if (!fs.existsSync(BACKUP_DIR)) return;
    const entries = fs.readdirSync(BACKUP_DIR)
      .filter(n => /^prod-data-/.test(n))
      .map(n => ({ name: n, full: path.join(BACKUP_DIR, n) }));
    if (entries.length <= KEEP_DAILY) return;
    // Sort by mtime descending; drop everything beyond the most recent N.
    entries.sort((a, b) => fs.statSync(b.full).mtimeMs - fs.statSync(a.full).mtimeMs);
    const drop = entries.slice(KEEP_DAILY);
    for (const e of drop) {
      try {
        fs.rmSync(e.full, { recursive: true, force: true });
        console.log('[backup] pruned old: ' + e.name);
      } catch (e) {
        console.warn('[backup] prune failed for ' + e.name + ': ' + e.message);
      }
    }
  } catch (e) {
    console.warn('[backup] prune sweep error:', e.message);
  }
}

function start() {
  if (global.__ncii_workers) global.__ncii_workers.backup = 'pending';
  if (timer) return;
  timer = cron.schedule(SCHEDULE, () => {
    runOnce().catch(e => {
      if (global.__ncii_workers) global.__ncii_workers.backup = 'failed';
      console.warn('[backup] tick error:', e.message);
    });
  });
  // Run once on startup so /health shows a fresh backup window even
  // right after a deploy. Don't await — let the rest of the boot
  // finish first.
  setTimeout(() => {
    runOnce().catch(e => console.warn('[backup] initial run error:', e.message));
  }, 30 * 1000);
  console.log('[backup] worker started (daily at 02:30, keep ' + KEEP_DAILY + ' backups)');
}

module.exports = { start, runOnce, SCHEDULE };