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

/**
 * Phase 3 per-space SSH isolation: migration backfill (spec §11).
 *
 * Rule (fully-independent model):
 *  - user-owned connections (owner_id = <real user>) with space_id IS NULL get
 *    migrated to that user's personal space (created if missing).
 *  - system connections (owner_id IS NULL) stay space_id NULL (invisible to
 *    spaces; reachable only by no-space jobs via the access-gate fallback).
 *  - owner_id is preserved (it drives the per-user DEK; no per-space DEK).
 *  - idempotent: a second run changes nothing.
 */
describe('migrate: ssh_connections space_id backfill', () => {
  let db: Database.Database;

  beforeEach(() => {
    process.env.MCP_ENCRYPTION_KEY = 'a'.repeat(64);
    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 + ssh_connections (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();
    delete process.env.MCP_ENCRYPTION_KEY;
  });

  function insertConn(id: string, ownerId: string | null): void {
    db.prepare(
      `INSERT INTO ssh_connections (id, owner_id, label, host, port, username,
         private_key_enc, key_fingerprint, remote_path_prefix, created_at, updated_at)
       VALUES (?, ?, ?, 'h', 22, 'u', X'01', 'SHA256:x', '/home/u', datetime('now'), datetime('now'))`,
    ).run(id, ownerId, id);
  }

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

  function ownerOf(connId: string): string | null {
    return (
      db.prepare('SELECT owner_id FROM ssh_connections WHERE id = ?').get(connId) as {
        owner_id: string | null;
      }
    ).owner_id;
  }

  it("migrates a per-user connection into its owner's personal space; system stays NULL", () => {
    insertConn('alice-conn', 'alice');
    insertConn('bob-conn', 'bob');
    insertConn('system-conn', null);

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

    const aliceSpace = spaceOf('alice-conn');
    const bobSpace = spaceOf('bob-conn');
    expect(aliceSpace).not.toBeNull();
    expect(bobSpace).not.toBeNull();
    // Different owners → different personal spaces.
    expect(aliceSpace).not.toBe(bobSpace);
    // owner_id is preserved (still drives the per-user DEK).
    expect(ownerOf('alice-conn')).toBe('alice');
    // 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' });

    // System connection is intentionally left NULL (invisible to spaces).
    expect(spaceOf('system-conn')).toBeNull();
    expect(ownerOf('system-conn')).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();
    insertConn('alice-conn', 'alice');

    runMigrations(db);

    expect(spaceOf('alice-conn')).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)', () => {
    insertConn('alice-conn', 'alice');
    runMigrations(db);
    const firstSpace = spaceOf('alice-conn');

    runMigrations(db); // second pass

    expect(spaceOf('alice-conn')).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);
  });
});
