// engine/db/migrations/008-small-state.js
//
// Phase 7: move the four remaining small state files into SQL in one
// migration. Each gets its own table — the files are too dissimilar to
// share schemas — but they ship together because they're all simple,
// low-traffic singletons.
//
//   engine/schedule-runs.json      -> schedule_runs
//   engine/pipeline-runs.json      -> pipeline_runs
//   engine/managed-processes.json  -> managed_processes
//   engine/worktree-pool.json      -> worktree_pool

const path = require('path');
const fs = require('fs');

function _resolveMinionsDir() {
  // Canonical order: MINIONS_TEST_DIR wins over MINIONS_HOME (matches
  // engine/db/index.js + migrations 002/003/004). Reversing this lets a
  // test that sets both env vars open the DB at MINIONS_TEST_DIR but
  // backfill its tables from MINIONS_HOME — cross-directory corruption.
  const envHome = process.env.MINIONS_TEST_DIR || process.env.MINIONS_HOME;
  if (envHome) return envHome;
  try { return require('../../shared').MINIONS_DIR; } catch { return null; }
}

function _toMs(v) {
  if (v == null) return null;
  if (typeof v === 'number') return Number.isFinite(v) ? v : null;
  const parsed = Date.parse(v);
  return Number.isFinite(parsed) ? parsed : null;
}

function _readJsonOr(filePath, fallback) {
  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    return JSON.parse(raw);
  } catch { return fallback; }
}

module.exports = {
  version: 8,
  description: 'schedule_runs + pipeline_runs + managed_processes + worktree_pool',
  up(db) {
    db.exec(`
      CREATE TABLE schedule_runs (
        schedule_id  TEXT PRIMARY KEY,
        data         TEXT NOT NULL,
        updated_at   INTEGER NOT NULL
      );

      CREATE TABLE pipeline_runs (
        pipeline_id  TEXT NOT NULL,
        run_id       TEXT NOT NULL,
        status       TEXT,
        started_at   INTEGER,
        data         TEXT NOT NULL,
        updated_at   INTEGER NOT NULL,
        PRIMARY KEY (pipeline_id, run_id)
      );
      CREATE INDEX idx_pipeline_runs_status   ON pipeline_runs(status);
      CREATE INDEX idx_pipeline_runs_pipeline ON pipeline_runs(pipeline_id);

      CREATE TABLE managed_processes (
        name        TEXT PRIMARY KEY,
        data        TEXT NOT NULL,
        updated_at  INTEGER NOT NULL
      );

      CREATE TABLE worktree_pool (
        entry_id    TEXT PRIMARY KEY,
        data        TEXT NOT NULL,
        updated_at  INTEGER NOT NULL
      );
    `);

    const minionsDir = _resolveMinionsDir();
    if (!minionsDir) return;
    const now = Date.now();
    let inserted = 0;

    // ── schedule_runs ──────────────────────────────────────────────────────
    {
      const raw = _readJsonOr(path.join(minionsDir, 'engine', 'schedule-runs.json'), null);
      if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
        const ins = db.prepare(`INSERT INTO schedule_runs (schedule_id, data, updated_at) VALUES (?, ?, ?)`);
        for (const [scheduleId, value] of Object.entries(raw)) {
          try {
            ins.run(scheduleId, JSON.stringify(value), now);
            inserted += 1;
          } catch { /* duplicate / corrupt — skip */ }
        }
      }
    }

    // ── pipeline_runs ──────────────────────────────────────────────────────
    {
      const raw = _readJsonOr(path.join(minionsDir, 'engine', 'pipeline-runs.json'), null);
      if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
        const ins = db.prepare(`
          INSERT INTO pipeline_runs (pipeline_id, run_id, status, started_at, data, updated_at)
          VALUES (?, ?, ?, ?, ?, ?)
        `);
        for (const [pipelineId, runs] of Object.entries(raw)) {
          if (!Array.isArray(runs)) continue;
          for (const run of runs) {
            if (!run || !run.runId) continue;
            try {
              ins.run(
                pipelineId,
                String(run.runId),
                run.status || null,
                _toMs(run.startedAt),
                JSON.stringify(run),
                now,
              );
              inserted += 1;
            } catch { /* duplicate / corrupt — skip */ }
          }
        }
      }
    }

    // ── managed_processes ──────────────────────────────────────────────────
    {
      const raw = _readJsonOr(path.join(minionsDir, 'engine', 'managed-processes.json'), null);
      if (raw && Array.isArray(raw.specs)) {
        const ins = db.prepare(`INSERT INTO managed_processes (name, data, updated_at) VALUES (?, ?, ?)`);
        for (const spec of raw.specs) {
          if (!spec || typeof spec !== 'object' || !spec.name) continue;
          try {
            ins.run(String(spec.name), JSON.stringify(spec), now);
            inserted += 1;
          } catch { /* duplicate name — skip */ }
        }
      }
    }

    // ── worktree_pool ──────────────────────────────────────────────────────
    {
      const raw = _readJsonOr(path.join(minionsDir, 'engine', 'worktree-pool.json'), null);
      if (raw && Array.isArray(raw.entries)) {
        const ins = db.prepare(`INSERT INTO worktree_pool (entry_id, data, updated_at) VALUES (?, ?, ?)`);
        for (const entry of raw.entries) {
          if (!entry || typeof entry !== 'object') continue;
          // Worktree pool entries don't have an obvious unique id field;
          // use the worktree path (always unique within a single pool).
          const id = entry.path || entry.id || JSON.stringify(entry).slice(0, 64);
          try {
            ins.run(String(id), JSON.stringify(entry), now);
            inserted += 1;
          } catch { /* duplicate — skip */ }
        }
      }
    }

    // eslint-disable-next-line no-console
    console.log(`[db-migrate] v8: backfilled ${inserted} small-state rows; JSON files kept as dual-write mirrors`);
  },
};
