import { test } from 'node:test';
import assert from 'node:assert/strict';
import { runMigrations } from '../src/migrate.js';
import { createManager } from '../src/plugin-manager.js';

// Dialog modules import supabase.js, which exits without these (loadPlugins for
// the real dialog manifest in the last test touches them transitively).
process.env.SUPABASE_URL ||= 'http://localhost';
process.env.SUPABASE_SERVICE_KEY ||= 'x';
process.env.JWT_SECRET ||= 'x';
process.env.SITE_DIR ||= '/tmp/nosite';

// A fake SQL executor over an in-memory schema_migrations set. Records every
// non-bookkeeping statement so we can assert what actually ran.
function fakeExec(seed = []) {
  const applied = new Set(seed);
  const ran = [];
  const exec = async (text, params) => {
    if (/^\s*SELECT 1 FROM public\.schema_migrations/i.test(text)) {
      return applied.has(params[0]) ? [{ '?column?': 1 }] : [];
    }
    if (/^\s*INSERT INTO public\.schema_migrations/i.test(text)) { applied.add(params[0]); return []; }
    if (/^\s*(CREATE TABLE IF NOT EXISTS public\.schema_migrations|BEGIN|COMMIT|ROLLBACK)/i.test(text)) return [];
    ran.push(text); // the actual migration file SQL
    return [];
  };
  return { exec, ran, applied };
}

test('no declared migrations → no-op, never needs a DB', async () => {
  const mgr = createManager([{ name: 'kgva-ish' }]); // no migrations
  const res = await runMigrations(mgr, {}); // no exec, no databaseUrl — must not throw
  assert.deepEqual(res, { applied: [], skipped: [], pending: false });
});

test('declared migrations but no DATABASE_URL → pending, nothing applied', async () => {
  const mgr = createManager([{ name: 'p', migrations: ['001.sql'], __dir: new URL('file:///x/') }]);
  const warns = [];
  const res = await runMigrations(mgr, { log: { warn: (m) => warns.push(m) } });
  assert.equal(res.pending, true);
  assert.deepEqual(res.skipped, ['p/001.sql']);
  assert.equal(res.applied.length, 0);
  assert.match(warns[0], /DATABASE_URL fehlt/);
});

test('fresh apply: real dialog migration runs once and is recorded', async () => {
  const { loadPlugins } = await import('../src/plugin-manager.js');
  const mgr = await loadPlugins(['dialog']); // real manifest → real 001_dialog.sql url
  const fx = fakeExec();
  const res = await runMigrations(mgr, { exec: fx.exec, log: {} });
  assert.deepEqual(res.applied, ['dialog/001_dialog.sql']);
  assert.deepEqual(res.skipped, []);
  assert.equal(fx.ran.length, 1);
  assert.match(fx.ran[0], /create table if not exists public\.forums/i); // the captured DDL
  assert.equal(fx.applied.has('dialog/001_dialog.sql'), true);
});

test('idempotent: already-recorded migration is skipped, file SQL not re-run', async () => {
  const { loadPlugins } = await import('../src/plugin-manager.js');
  const mgr = await loadPlugins(['dialog']);
  const fx = fakeExec(['dialog/001_dialog.sql']); // pretend already applied
  const res = await runMigrations(mgr, { exec: fx.exec, log: {} });
  assert.deepEqual(res.skipped, ['dialog/001_dialog.sql']);
  assert.deepEqual(res.applied, []);
  assert.equal(fx.ran.length, 0); // file SQL never executed again
});

test('a failing migration rolls back and surfaces the id', async () => {
  const mgr = createManager([{ name: 'boom', migrations: ['x.sql'], __dir: new URL('file:///does-not-exist/') }]);
  const calls = [];
  const exec = async (text) => {
    calls.push(text.trim().split(/\s+/).slice(0, 2).join(' '));
    return [];
  };
  // readFile on the missing url throws before BEGIN; ensure the error names the id.
  await assert.rejects(() => runMigrations(mgr, { exec, log: {} }), /boom\/x\.sql/);
});
