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

/**
 * One-shot backfill: existing rows that belong to a space must be forced to
 * visibility='private' (and scope cleared) so they cannot leak to non-members
 * via the org/public visibility paths. Idempotent + non-space rows untouched.
 * spec: docs/superpowers/specs/2026-06-19-shared-space-design.md
 */
describe('migratePrivatizeSpaceRows', () => {
  let dir = '';
  let repo: Repository;

  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), 'migrate-privatize-'));
    repo = new Repository(join(dir, 'db.sqlite'));
  });

  afterEach(() => {
    repo.close();
    rmSync(dir, { recursive: true, force: true });
  });

  function rowVis(table: string, id: number | string): { visibility: string; scope: string | null } {
    const r = repo
      .getDb()
      .prepare(`SELECT visibility, visibility_scope_org_id AS scope FROM ${table} WHERE id = ?`)
      .get(id) as { visibility: string; scope: string | null };
    return r;
  }

  it('privatizes public + org space rows across local_tasks/jobs/scheduled_tasks; non-space rows untouched; idempotent', async () => {
    const db = repo.getDb();
    const owner = repo.createUser({ email: 'o@x.com', name: 'o', role: 'user', status: 'active' });
    const space = await repo.createSpace({ kind: 'case', title: 'S', ownerId: owner.id, visibility: 'private' });

    // Seed rows DIRECTLY at non-private visibility (simulating legacy rows that
    // predate the "space tasks forced private" rule).
    db.prepare(
      `INSERT INTO local_tasks (id, title, body, owner_id, visibility, visibility_scope_org_id, space_id)
       VALUES (?, ?, ?, ?, ?, ?, ?)`,
    ).run(9001, 'pub', 'b', owner.id, 'public', null, space.id);
    db.prepare(
      `INSERT INTO local_tasks (id, title, body, owner_id, visibility, visibility_scope_org_id, space_id)
       VALUES (?, ?, ?, ?, ?, ?, ?)`,
    ).run(9002, 'org', 'b', owner.id, 'org', '42', space.id);
    // Non-space public task that MUST stay public.
    db.prepare(
      `INSERT INTO local_tasks (id, title, body, owner_id, visibility, visibility_scope_org_id, space_id)
       VALUES (?, ?, ?, ?, ?, ?, ?)`,
    ).run(9003, 'nonspace', 'b', owner.id, 'public', null, null);
    // jobs + scheduled_tasks space rows at non-private visibility.
    db.prepare(
      `INSERT INTO jobs (id, repo, issue_number, status, piece_name, instruction, owner_id, visibility, visibility_scope_org_id, space_id)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run('job-1', 'local/task-9001', 9001, 'succeeded', 'chat', 'x', owner.id, 'org', '42', space.id);
    db.prepare(
      `INSERT INTO scheduled_tasks (id, body, piece_name, profile, output_format, cron_expression, next_run_at, owner_id, visibility, visibility_scope_org_id, space_id)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
    ).run(9501, 'b', 'chat', 'auto', 'markdown', '0 0 * * *', new Date().toISOString(), owner.id, 'public', null, space.id);

    runMigrations(db);

    // space rows privatized + scope cleared
    expect(rowVis('local_tasks', 9001)).toEqual({ visibility: 'private', scope: null });
    expect(rowVis('local_tasks', 9002)).toEqual({ visibility: 'private', scope: null });
    expect(rowVis('jobs', 'job-1')).toEqual({ visibility: 'private', scope: null });
    expect(rowVis('scheduled_tasks', 9501)).toEqual({ visibility: 'private', scope: null });
    // non-space public task untouched
    expect(rowVis('local_tasks', 9003)).toEqual({ visibility: 'public', scope: null });

    // idempotent: a second run changes nothing further
    expect(() => runMigrations(db)).not.toThrow();
    expect(rowVis('local_tasks', 9001)).toEqual({ visibility: 'private', scope: null });
    expect(rowVis('local_tasks', 9003)).toEqual({ visibility: 'public', scope: null });
  });
});
