import { afterEach, describe, expect, it } from 'vitest';
import Database from 'better-sqlite3';
import { mkdtempSync, rmSync, readFileSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from './repository.js';
import { runMigrations } from './migrate.js';

describe('browser session persistence migrations', () => {
  let tempDir = '';

  afterEach(() => {
    if (tempDir) {
      rmSync(tempDir, { recursive: true, force: true });
      tempDir = '';
    }
  });

  it('creates the new tables and columns via Repository constructor', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-bsm-'));
    const dbPath = join(tempDir, 'orchestrator.db');
    // Repository constructor runs schema.sql + idempotent migrations.
    new Repository(dbPath);

    const db = new Database(dbPath, { readonly: true });
    try {
      const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{ name: string }>;
      const names = new Set(tables.map(t => t.name));
      expect(names.has('user_deks')).toBe(true);
      expect(names.has('browser_session_profiles')).toBe(true);
      expect(names.has('browser_session_audit')).toBe(true);

      const localCols = db.prepare("PRAGMA table_info('local_tasks')").all() as Array<{ name: string }>;
      expect(localCols.some(c => c.name === 'browser_session_profile_id')).toBe(true);

      const scheduledCols = db.prepare("PRAGMA table_info('scheduled_tasks')").all() as Array<{ name: string }>;
      expect(scheduledCols.some(c => c.name === 'browser_session_profile_id')).toBe(true);

      const jobCols = db.prepare("PRAGMA table_info('jobs')").all() as Array<{ name: string }>;
      expect(jobCols.some(c => c.name === 'browser_session_profile_id')).toBe(true);
    } finally {
      db.close();
    }
  });

  it('schema.sql alone (without migrations) creates the new tables', () => {
    const db = new Database(':memory:');
    try {
      const schema = readFileSync(new URL('./schema.sql', import.meta.url), 'utf-8');
      db.exec(schema);

      const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{ name: string }>;
      const names = new Set(tables.map(t => t.name));
      expect(names.has('user_deks')).toBe(true);
      expect(names.has('browser_session_profiles')).toBe(true);
      expect(names.has('browser_session_audit')).toBe(true);
    } finally {
      db.close();
    }
  });

  it('adds space_id column + idx_bsp_space index via the triple-path (Repository ctor)', () => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-bsm-space-'));
    const dbPath = join(tempDir, 'orchestrator.db');
    new Repository(dbPath);

    const db = new Database(dbPath, { readonly: true });
    try {
      const cols = db.prepare("PRAGMA table_info('browser_session_profiles')").all() as Array<{ name: string }>;
      expect(cols.some(c => c.name === 'space_id')).toBe(true);
      const idx = db.prepare("PRAGMA index_list('browser_session_profiles')").all() as Array<{ name: string }>;
      expect(idx.some(i => i.name === 'idx_bsp_space')).toBe(true);
    } finally {
      db.close();
    }
  });

  it('schema.sql alone declares space_id + idx_bsp_space', () => {
    const db = new Database(':memory:');
    try {
      db.exec(readFileSync(new URL('./schema.sql', import.meta.url), 'utf-8'));
      const cols = db.prepare("PRAGMA table_info('browser_session_profiles')").all() as Array<{ name: string }>;
      expect(cols.some(c => c.name === 'space_id')).toBe(true);
      const idx = db.prepare("PRAGMA index_list('browser_session_profiles')").all() as Array<{ name: string }>;
      expect(idx.some(i => i.name === 'idx_bsp_space')).toBe(true);
    } finally {
      db.close();
    }
  });


  it("opens a legacy DB whose browser_session_profiles table lacks space_id before schema.sql creates idx_bsp_space", () => {
    tempDir = mkdtempSync(join(tmpdir(), "maestro-bsm-legacy-"));
    const dbPath = join(tempDir, "orchestrator.db");
    const legacy = new Database(dbPath);
    try {
      legacy.exec(`
        CREATE TABLE browser_session_profiles (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          owner_id TEXT NOT NULL,
          label TEXT NOT NULL,
          start_url TEXT NOT NULL,
          match_patterns TEXT NOT NULL DEFAULT "[]",
          storage_origins TEXT NOT NULL DEFAULT "[]",
          logged_in_selector TEXT,
          login_url_patterns TEXT NOT NULL DEFAULT "[]",
          encrypted_state_blob BLOB,
          state_version INTEGER NOT NULL DEFAULT 0,
          playwright_version TEXT,
          status TEXT NOT NULL DEFAULT "pending",
          last_saved_at TEXT,
          last_used_at TEXT,
          last_validated_at TEXT,
          last_error TEXT,
          created_at TEXT,
          updated_at TEXT
        );
      `);
    } finally {
      legacy.close();
    }

    const repo = new Repository(dbPath);
    const cols = repo.getDb().prepare("PRAGMA table_info(browser_session_profiles)").all() as Array<{ name: string }>;
    expect(cols.some((c) => c.name === "space_id")).toBe(true);
    const idx = repo.getDb().prepare("PRAGMA index_list(browser_session_profiles)").all() as Array<{ name: string }>;
    expect(idx.some((i) => i.name === "idx_bsp_space")).toBe(true);
  });

  it('backfills legacy profiles into the owner personal space, idempotently; admin-global stays NULL', () => {
    const db = new Database(':memory:');
    try {
      // Simulate a LEGACY DB: browser_session_profiles WITHOUT space_id.
      db.exec(`
        CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT, role TEXT, status TEXT,
          created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')));
        CREATE TABLE spaces (
          id TEXT PRIMARY KEY, kind TEXT, title TEXT, description TEXT DEFAULT '',
          owner_id TEXT, visibility TEXT DEFAULT 'private',
          visibility_scope_org_id TEXT, status TEXT DEFAULT 'open',
          created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')));
        CREATE TABLE browser_session_profiles (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          owner_id TEXT NOT NULL,
          label TEXT NOT NULL, start_url TEXT NOT NULL,
          match_patterns TEXT DEFAULT '[]', storage_origins TEXT DEFAULT '[]',
          logged_in_selector TEXT, login_url_patterns TEXT DEFAULT '[]',
          status TEXT DEFAULT 'pending',
          created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')));
      `);
      db.prepare("INSERT INTO users (id, email, role, status) VALUES ('alice','a@x','active','active')").run();
      const pId = db.prepare(
        "INSERT INTO browser_session_profiles (owner_id, label, start_url) VALUES ('alice','A','https://a.com')",
      ).run().lastInsertRowid as number;

      // First migration run: adds space_id + backfills alice's profile into her personal space.
      runMigrations(db);

      const afterCols = db.prepare("PRAGMA table_info('browser_session_profiles')").all() as Array<{ name: string }>;
      expect(afterCols.some(c => c.name === 'space_id')).toBe(true);

      const personal = db.prepare("SELECT id FROM spaces WHERE owner_id = 'alice' AND kind = 'personal'").get() as { id: string } | undefined;
      expect(personal).toBeTruthy();
      const profSpace = (db.prepare('SELECT space_id FROM browser_session_profiles WHERE id = ?').get(pId) as { space_id: string | null }).space_id;
      expect(profSpace).toBe(personal!.id);

      // Re-run is a no-op: space_id unchanged, no duplicate personal space created.
      runMigrations(db);
      const profSpace2 = (db.prepare('SELECT space_id FROM browser_session_profiles WHERE id = ?').get(pId) as { space_id: string | null }).space_id;
      expect(profSpace2).toBe(personal!.id);
      const personalCount = (db.prepare("SELECT COUNT(*) AS n FROM spaces WHERE owner_id = 'alice' AND kind = 'personal'").get() as { n: number }).n;
      expect(personalCount).toBe(1);
    } finally {
      db.close();
    }
  });

  it('rejects invalid status / action / result values', () => {
    const tmp = mkdtempSync(join(tmpdir(), 'crg-mig-'));
    const dbPath = join(tmp, 'test.db');
    new Repository(dbPath);
    const db = new Database(dbPath);

    expect(() => db.prepare(
      `INSERT INTO browser_session_profiles (owner_id, label, start_url, status)
       VALUES ('u1','x','https://x.com','garbage')`
    ).run()).toThrow();

    expect(() => db.prepare(
      `INSERT INTO browser_session_audit (action, result) VALUES ('garbage','success')`
    ).run()).toThrow();

    expect(() => db.prepare(
      `INSERT INTO browser_session_audit (action, result) VALUES ('create','garbage')`
    ).run()).toThrow();

    db.close();
    rmSync(tmp, { recursive: true, force: true });
  });
});
