// engine/db/migrations/002-dispatches.js
//
// Phase 1: move engine/dispatch.json into a `dispatches` table.
//
// Schema is intentionally hybrid: a small set of indexed projection columns
// (status, agent, type, timestamps) for hot filters/sorts, plus a single
// `data` TEXT column holding the full record JSON. This way the
// `mutateDispatch(fn)` API contract — "mutator receives the section-shaped
// object, mutates in place, returns" — round-trips every field cleanly
// without us needing to enumerate every possible column ahead of time.
// New fields the engine adds in a future commit land in `data`
// automatically.
//
// Backfill: read engine/dispatch.json's four sections (pending / active /
// completed / review), INSERT one row per dispatch with `status` derived
// from the section name. Timestamps are parsed from ISO strings to
// milliseconds since epoch (matches the rest of the codebase). The JSON
// file is renamed to engine/dispatch.json.pre-sql-<ts> for rollback —
// NOT deleted. A separate `minions db rollback-to-json` command (next
// phase) will read the table and reconstruct the JSON if the user pins
// to a pre-migration version.

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

function _resolveMinionsDir() {
  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;
}

module.exports = {
  version: 2,
  description: 'dispatches: schema + dispatch.json backfill',
  up(db) {
    db.exec(`
      CREATE TABLE dispatches (
        id            TEXT PRIMARY KEY,
        status        TEXT NOT NULL,
        agent         TEXT,
        type          TEXT,
        created_at    INTEGER,
        started_at    INTEGER,
        completed_at  INTEGER,
        data          TEXT NOT NULL,
        updated_at    INTEGER NOT NULL
      );
      CREATE INDEX idx_dispatches_status            ON dispatches(status);
      CREATE INDEX idx_dispatches_agent             ON dispatches(agent);
      CREATE INDEX idx_dispatches_status_completed  ON dispatches(status, completed_at DESC);
      CREATE INDEX idx_dispatches_status_created    ON dispatches(status, created_at DESC);
    `);

    const minionsDir = _resolveMinionsDir();
    if (!minionsDir) return;
    const dispatchPath = path.join(minionsDir, 'engine', 'dispatch.json');
    if (!fs.existsSync(dispatchPath)) return;

    let raw;
    try { raw = JSON.parse(fs.readFileSync(dispatchPath, 'utf8')); }
    catch (e) {
      // Corrupt JSON — abort the migration, surfacing the error. The user
      // can fix the file (or restore from the .backup sidecar safeWrite
      // keeps) and re-run the engine; the migration will retry.
      throw new Error(`engine/db/002-dispatches: cannot parse dispatch.json: ${e.message}`);
    }

    const now = Date.now();
    const insert = db.prepare(`
      INSERT INTO dispatches (id, status, agent, type, created_at, started_at, completed_at, data, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);

    let inserted = 0;
    for (const status of ['pending', 'active', 'completed', 'review']) {
      const rows = raw[status];
      if (!Array.isArray(rows)) continue;
      for (const d of rows) {
        if (!d || typeof d !== 'object' || !d.id) continue;
        insert.run(
          String(d.id),
          status,
          d.agent || null,
          d.type || null,
          _toMs(d.created_at),
          _toMs(d.started_at),
          _toMs(d.completed_at),
          JSON.stringify(d),
          now,
        );
        inserted += 1;
      }
    }

    // dispatch.json stays on disk as a dual-write mirror — every successful
    // mutateDispatch refreshes it from SQL via dispatch-store._mirrorJsonFromSql.
    // SQL is the source of truth; the file is a read-only derivative kept
    // for backward compatibility with legacy direct-readers (engine/routing.js,
    // engine/queries.js fallback, test infrastructure). Rollback path: if a
    // user pins back to a pre-Phase-1 release, dispatch.json already contains
    // the latest committed state (last mirror write), and the SQL table can
    // be discarded.
    // eslint-disable-next-line no-console
    console.log(`[db-migrate] v2: backfilled ${inserted} dispatches; dispatch.json kept as dual-write mirror`);
  },
};
