/**
 * Local-auth repository layer: password hashing, local account creation,
 * the shared `local` system admin, and the user-deletion guards.
 *
 * See docs/superpowers/plans/2026-06-09-local-auth.md.
 */
import { afterEach, describe, it, expect, beforeEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from './repository.js';
import { runMigrations } from './migrate.js';

describe('Repository local-auth', () => {
  let tempDir = '';
  let repo: Repository;

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'maestro-localauth-'));
    repo = new Repository(join(tempDir, 'orchestrator.db'));
    runMigrations(repo.getDb());
  });

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

  // ── password hashing ──────────────────────────────────────────

  it('setLocalPassword + verifyLocalPassword round-trips', () => {
    const u = repo.createUser({ email: 'a@x.com', name: 'A', role: 'user', status: 'active' });
    repo.setLocalPassword(u.id, 'correct horse battery staple');
    expect(repo.verifyLocalPassword(u.id, 'correct horse battery staple')).toBe(true);
    expect(repo.verifyLocalPassword(u.id, 'wrong')).toBe(false);
  });

  it('verifyLocalPassword returns false for a user with no credential', () => {
    const u = repo.createUser({ email: 'b@x.com', name: 'B', role: 'user', status: 'active' });
    expect(repo.verifyLocalPassword(u.id, 'anything')).toBe(false);
  });

  it('setLocalPassword is idempotent-overwrite (re-set changes the password)', () => {
    const u = repo.createUser({ email: 'c@x.com', name: 'C', role: 'user', status: 'active' });
    repo.setLocalPassword(u.id, 'first');
    repo.setLocalPassword(u.id, 'second');
    expect(repo.verifyLocalPassword(u.id, 'first')).toBe(false);
    expect(repo.verifyLocalPassword(u.id, 'second')).toBe(true);
  });

  it('stores a per-user random salt (two users, same password → different hash)', () => {
    const u1 = repo.createUser({ email: 'd@x.com', name: 'D', role: 'user', status: 'active' });
    const u2 = repo.createUser({ email: 'e@x.com', name: 'E', role: 'user', status: 'active' });
    repo.setLocalPassword(u1.id, 'same');
    repo.setLocalPassword(u2.id, 'same');
    const db = repo.getDb();
    const r1 = db.prepare('SELECT password_hash, salt FROM local_credentials WHERE user_id=?').get(u1.id) as { password_hash: string; salt: string };
    const r2 = db.prepare('SELECT password_hash, salt FROM local_credentials WHERE user_id=?').get(u2.id) as { password_hash: string; salt: string };
    expect(r1.salt).not.toBe(r2.salt);
    expect(r1.password_hash).not.toBe(r2.password_hash);
  });

  // ── createLocalUser ───────────────────────────────────────────

  it('createLocalUser creates a pending user with a local identity + password', () => {
    const u = repo.createLocalUser({ email: 'new@x.com', password: 'pw12345', role: 'user', status: 'pending' });
    expect(u.email).toBe('new@x.com');
    expect(u.status).toBe('pending');
    expect(repo.verifyLocalPassword(u.id, 'pw12345')).toBe(true);
    const idn = repo.getDb().prepare("SELECT provider, provider_id FROM oauth_accounts WHERE user_id=? AND provider='local'").get(u.id) as { provider: string; provider_id: string };
    expect(idn.provider).toBe('local');
    expect(idn.provider_id).toBe('new@x.com');
  });

  it('createLocalUser REJECTS an email that already exists (no account takeover)', () => {
    repo.createUser({ email: 'taken@x.com', name: 'T', role: 'user', status: 'active' });
    expect(() => repo.createLocalUser({ email: 'taken@x.com', password: 'pw', role: 'user', status: 'pending' }))
      .toThrow(/exist/i);
  });

  // ── upsertLocalSystemAdmin (shared 'local' identity) ──────────

  it('upsertLocalSystemAdmin creates the shared id=local admin (active)', () => {
    const u = repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'adminpw' });
    expect(u.id).toBe('local');
    expect(u.role).toBe('admin');
    expect(u.status).toBe('active');
    expect(repo.verifyLocalPassword('local', 'adminpw')).toBe(true);
  });

  it('upsertLocalSystemAdmin is idempotent and keeps id=local across re-seeds', () => {
    repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'pw1' });
    const again = repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'pw2' });
    expect(again.id).toBe('local');
    // password updated on re-seed
    expect(repo.verifyLocalPassword('local', 'pw2')).toBe(true);
    // exactly one users row with id=local
    const cnt = repo.getDb().prepare("SELECT COUNT(*) c FROM users WHERE id='local'").get() as { c: number };
    expect(cnt.c).toBe(1);
  });

  it('local-owned data survives because the admin IS the local owner (id=local)', () => {
    // Simulate no-auth data owned by 'local', then seed the admin onto it.
    repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'pw' });
    expect(repo.getUserById('local')?.role).toBe('admin');
  });

  // ── deleteUser guards ─────────────────────────────────────────

  it('deleteUser REFUSES to delete the local/system user', () => {
    repo.upsertLocalSystemAdmin({ email: 'admin@x.com', password: 'pw' });
    expect(() => repo.deleteUser('local')).toThrow(/local|system/i);
    expect(repo.getUserById('local')).not.toBeNull();
  });

  it('deleteUser cascades local_credentials (FK ON DELETE CASCADE)', () => {
    const u = repo.createLocalUser({ email: 'z@x.com', password: 'pw', role: 'user', status: 'active' });
    expect(repo.verifyLocalPassword(u.id, 'pw')).toBe(true);
    repo.deleteUser(u.id);
    const row = repo.getDb().prepare('SELECT 1 FROM local_credentials WHERE user_id=?').get(u.id);
    expect(row).toBeUndefined();
  });
});
