#!/usr/bin/env node
'use strict';

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

function loadBetterSqlite() {
  try { return require('../claude-task-manager/node_modules/better-sqlite3'); } catch {}
  try { return require('../wall-e/node_modules/better-sqlite3'); } catch {}
  return require('better-sqlite3');
}

const Database = loadBetterSqlite();

function removeFile(file) {
  try { fs.rmSync(file, { force: true }); } catch {}
}

function removeDbFamily(file) {
  removeFile(file);
  removeFile(file + '-wal');
  removeFile(file + '-shm');
}

function loadOptionalVec(db) {
  try {
    const sqliteVec = require('../wall-e/node_modules/sqlite-vec');
    sqliteVec.load(db);
  } catch {}
}

function logDebug(label, message) {
  if (process.env.SQLITE_SNAPSHOT_DEBUG === '1') {
    console.warn(`  ${label}: ${message}`);
  }
}

function quickCheck(dbPath) {
  let db;
  try {
    db = new Database(dbPath, { readonly: true, fileMustExist: true });
    loadOptionalVec(db);
    const rows = db.pragma('quick_check');
    const value = rows?.[0] ? Object.values(rows[0])[0] : '';
    return value === 'ok' ? { ok: true } : { ok: false, error: String(value || 'quick_check failed') };
  } catch (err) {
    return { ok: false, error: err.message };
  } finally {
    try { if (db) db.close(); } catch {}
  }
}

async function sqliteBackup(src, dest) {
  const db = new Database(src, { readonly: true, fileMustExist: true });
  try {
    loadOptionalVec(db);
    await db.backup(dest);
  } finally {
    db.close();
  }
}

function pipeRecover(input, output) {
  return new Promise((resolve, reject) => {
    removeFile(output);
    const sqlite = process.env.SQLITE3_BIN || '/usr/bin/sqlite3';
    const recover = spawn(sqlite, [input, '.recover'], { stdio: ['ignore', 'pipe', 'inherit'] });
    const apply = spawn(sqlite, [output], { stdio: ['pipe', 'ignore', 'inherit'] });
    let recoverCode = null;
    let applyCode = null;
    let settled = false;
    const fail = (err) => {
      if (settled) return;
      settled = true;
      reject(err);
    };
    const maybeDone = () => {
      if (settled || recoverCode === null || applyCode === null) return;
      if (recoverCode !== 0) return fail(new Error(`sqlite3 .recover exited ${recoverCode}`));
      if (applyCode !== 0) return fail(new Error(`sqlite3 restore exited ${applyCode}`));
      settled = true;
      resolve();
    };
    recover.stdout.pipe(apply.stdin);
    recover.on('error', fail);
    apply.on('error', fail);
    recover.on('close', (code) => {
      recoverCode = code;
      if (code !== 0) try { apply.stdin.destroy(); } catch {}
      maybeDone();
    });
    apply.on('close', (code) => {
      applyCode = code;
      maybeDone();
    });
  });
}

async function main() {
  const [src, dest, label = 'SQLite'] = process.argv.slice(2);
  if (!src || !dest) {
    console.error('Usage: sqlite-snapshot.js <source.db> <dest.db> [label]');
    process.exit(2);
  }
  if (!fs.existsSync(src)) {
    console.log(`  ${label}: source missing (${src}) - starting fresh`);
    removeDbFamily(dest);
    return;
  }

  fs.mkdirSync(path.dirname(dest), { recursive: true });
  removeDbFamily(dest);
  const tmp = `${dest}.tmp-${process.pid}`;
  const recovered = `${dest}.recovered-${process.pid}`;
  removeDbFamily(tmp);
  removeDbFamily(recovered);

  try {
    await sqliteBackup(src, tmp);
    const check = quickCheck(tmp);
    if (check.ok) {
      fs.renameSync(tmp, dest);
      console.log(`  ${label}: snapshotted ${src} -> ${dest}`);
      return;
    }

    logDebug(label, `snapshot quick_check failed: ${check.error.slice(0, 160)}`);
    console.log(`  ${label}: snapshot required recovery - attempting sqlite .recover`);
    await pipeRecover(tmp, recovered);
    const recoveredCheck = quickCheck(recovered);
    if (!recoveredCheck.ok) throw new Error(`recovered DB failed quick_check: ${recoveredCheck.error.slice(0, 160)}`);
    fs.renameSync(recovered, dest);
    console.log(`  ${label}: recovered snapshot written to ${dest}`);
  } catch (err) {
    logDebug(label, `unable to create healthy snapshot: ${err.message}`);
    console.warn(`  ${label}: unable to create healthy snapshot - starting fresh`);
    removeDbFamily(dest);
  } finally {
    removeDbFamily(tmp);
    removeDbFamily(recovered);
  }
}

main().catch((err) => {
  console.error(`sqlite snapshot failed: ${err.stack || err.message}`);
  process.exit(1);
});
