import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { runMigrations } from './migrate.js';

/**
 * Phase 2 per-space MCP isolation: migration backfill.
 *
 * Rule (fully-independent model):
 *  - user-owned servers (owner_id = <real user>) with space_id IS NULL get
 *    migrated to that user's personal space (created if missing).
 *  - global servers (owner_id IS NULL) stay space_id NULL (invisible to spaces).
 *  - idempotent: a second run changes nothing.
 */
describe('migrate: mcp_servers space_id backfill', () => {
  let db: Database.Database;

  beforeEach(() => {
    db = new Database(':memory:');
    db.exec(`CREATE TABLE users (id TEXT PRIMARY KEY);`);
    db.exec(`CREATE TABLE jobs (id TEXT PRIMARY KEY, wait_reason TEXT);`);
    db.exec(`CREATE TABLE local_tasks (id INTEGER PRIMARY KEY AUTOINCREMENT);`);
    // First migration creates spaces + mcp_servers (with space_id column).
    runMigrations(db);
    db.prepare('INSERT INTO users(id) VALUES(?)').run('alice');
    db.prepare('INSERT INTO users(id) VALUES(?)').run('bob');
  });

  afterEach(() => db.close());

  function insertServer(id: string, ownerId: string | null): void {
    db.prepare(
      `INSERT INTO mcp_servers (id, name, url, oauth_client_id, oauth_client_secret_enc, auth_kind, owner_id, enabled)
       VALUES (?, ?, 'https://x.example/mcp', '', X'00', 'api_key', ?, 1)`,
    ).run(id, id, ownerId);
  }

  function spaceOf(serverId: string): string | null {
    return (db.prepare('SELECT space_id FROM mcp_servers WHERE id = ?').get(serverId) as { space_id: string | null })
      .space_id;
  }

  it('migrates a per-user server into its owner\'s personal space; global stays NULL', () => {
    insertServer('alice-srv', 'alice');
    insertServer('bob-srv', 'bob');
    insertServer('global-srv', null);

    runMigrations(db); // re-run triggers backfill

    const aliceSpace = spaceOf('alice-srv');
    const bobSpace = spaceOf('bob-srv');
    expect(aliceSpace).not.toBeNull();
    expect(bobSpace).not.toBeNull();
    // Different owners → different personal spaces.
    expect(aliceSpace).not.toBe(bobSpace);
    // The space row actually exists and is alice's personal space.
    const aliceSpaceRow = db
      .prepare("SELECT owner_id, kind FROM spaces WHERE id = ?")
      .get(aliceSpace) as { owner_id: string; kind: string };
    expect(aliceSpaceRow).toEqual({ owner_id: 'alice', kind: 'personal' });

    // Global server is intentionally left NULL (invisible to spaces).
    expect(spaceOf('global-srv')).toBeNull();
  });

  it('reuses an existing personal space rather than creating a duplicate', () => {
    db.prepare(
      `INSERT INTO spaces (id, kind, title, owner_id, visibility, status) VALUES ('alice-personal', 'personal', 'P', 'alice', 'private', 'open')`,
    ).run();
    insertServer('alice-srv', 'alice');

    runMigrations(db);

    expect(spaceOf('alice-srv')).toBe('alice-personal');
    const count = (db.prepare("SELECT COUNT(*) AS n FROM spaces WHERE owner_id = 'alice' AND kind = 'personal'").get() as { n: number }).n;
    expect(count).toBe(1);
  });

  it('is idempotent (a second run does not re-migrate or duplicate spaces)', () => {
    insertServer('alice-srv', 'alice');
    runMigrations(db);
    const firstSpace = spaceOf('alice-srv');

    runMigrations(db); // second pass

    expect(spaceOf('alice-srv')).toBe(firstSpace);
    const count = (db.prepare("SELECT COUNT(*) AS n FROM spaces WHERE owner_id = 'alice' AND kind = 'personal'").get() as { n: number }).n;
    expect(count).toBe(1);
  });
});
