import { describe, it, expect, beforeAll } from 'vitest';
import { execFileSync } from 'child_process';
import { mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import Database from 'better-sqlite3';

// Migration idempotency for the Better Auth backfill: seed legacy-shaped user
// rows, run migrate repeatedly, and assert the backfill is correct and never
// duplicates.

const dir = mkdtempSync(join(tmpdir(), 'ba-migrate-test-'));
const dbPath = join(dir, 'migrate.db');

function runMigrate() {
  execFileSync('npx', ['tsx', 'src/db/migrate.ts'], {
    env: {
      ...process.env,
      DB_TYPE: 'sqlite',
      DATABASE_URL: dbPath,
      REDIS_URL: '',
    },
    stdio: 'pipe',
  });
}

let db: Database.Database;

beforeAll(() => {
  // First run creates the schema
  runMigrate();
  db = new Database(dbPath);

  // Seed legacy-shaped users (pre-Better-Auth): password lives on users,
  // google_id links Google, '' marks guest accounts, suspended via status.
  const now = new Date().toISOString();
  const insert = db.prepare(
    `INSERT INTO users (id, email, password, name, role, is_claimed, google_id, account_status, token_version, created_at, updated_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)`
  );
  insert.run('legacy-argon', 'argon@old.py', '$argon2id$v=19$m=65536,t=3,p=4$fake', 'Argon', 'user', 1, null, 'active', now, now);
  insert.run('legacy-bcrypt', 'bcrypt@old.py', '$2a$10$fakebcryptfakebcryptfakebc', 'Bcrypt', 'admin', 1, null, 'active', now, now);
  insert.run('legacy-guest', 'guest@old.py', '', 'Guest', 'user', 1, null, 'active', now, now);
  insert.run('legacy-google', 'google@old.py', null, 'Google', 'user', 1, 'google-sub-123', 'active', now, now);
  insert.run('legacy-both', 'both@old.py', '$argon2id$v=19$m=65536,t=3,p=4$fake2', 'Both', 'user', 1, 'google-sub-456', 'active', now, now);
  insert.run('legacy-suspended', 'suspended@old.py', '$argon2id$v=19$m=65536,t=3,p=4$fake3', 'Bad', 'user', 1, null, 'suspended', now, now);

  // Second run performs the backfill against the seeded rows
  runMigrate();
}, 240_000);

describe('Better Auth migration backfill', () => {
  it('creates credential accounts for users with real passwords only', () => {
    const rows = db
      .prepare("SELECT user_id, password FROM auth_accounts WHERE provider_id = 'credential' ORDER BY user_id")
      .all() as any[];
    const byUser = new Map(rows.map((r) => [r.user_id, r.password]));

    expect(byUser.get('legacy-argon')).toContain('$argon2id$');
    expect(byUser.get('legacy-bcrypt')).toContain('$2a$');
    expect(byUser.get('legacy-both')).toContain('$argon2id$');
    expect(byUser.get('legacy-suspended')).toBeTruthy();
    // Guests ('' password) and Google-only users get no credential account
    expect(byUser.has('legacy-guest')).toBe(false);
    expect(byUser.has('legacy-google')).toBe(false);
  });

  it('creates google accounts from google_id', () => {
    const rows = db
      .prepare("SELECT user_id, account_id FROM auth_accounts WHERE provider_id = 'google' ORDER BY user_id")
      .all() as any[];
    expect(rows).toEqual([
      { user_id: 'legacy-both', account_id: 'google-sub-456' },
      { user_id: 'legacy-google', account_id: 'google-sub-123' },
    ]);
  });

  it('marks claimed legacy users email-verified, guests not', () => {
    const verified = (email: string) =>
      (db.prepare('SELECT email_verified FROM users WHERE email = ?').get(email) as any).email_verified;
    expect(verified('argon@old.py')).toBe(1);
    expect(verified('google@old.py')).toBe(1);
    expect(verified('guest@old.py')).toBe(0);
  });

  it('mirrors suspended accounts to banned', () => {
    const row = db.prepare('SELECT banned, ban_reason FROM users WHERE email = ?').get('suspended@old.py') as any;
    expect(row.banned).toBe(1);
    expect(row.ban_reason).toContain('suspended');
    const active = db.prepare('SELECT banned FROM users WHERE email = ?').get('argon@old.py') as any;
    expect(active.banned).toBe(0);
  });

  it('is idempotent: a third run adds nothing', () => {
    const count = () => (db.prepare('SELECT COUNT(*) AS n FROM auth_accounts').get() as any).n;
    const before = count();
    runMigrate();
    expect(count()).toBe(before);
  }, 60_000);
});
