import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { runMigrations } from '../db/migrate.js';
import { createConnectionRepo, type SshConnection } from './connection-repo.js';
import { createGrantsRepo } from './grants-repo.js';
import { createAccessResolver } from './access.js';

const validKey = 'a'.repeat(64);

function bootstrapDb(): Database.Database {
  process.env.MCP_ENCRYPTION_KEY = validKey;
  const db = new Database(':memory:');
  db.pragma('foreign_keys = ON');
  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);`);
  runMigrations(db);
  db.prepare(`INSERT INTO users(id) VALUES (?), (?), (?)`).run('alice', 'bob', 'admin');
  return db;
}

function mkConnection(db: Database.Database, ownerId: string | null): SshConnection {
  const repo = createConnectionRepo(db);
  return repo.create({
    ownerId,
    label: 'srv',
    host: 'h',
    port: 22,
    username: 'u',
    privateKeyEnc: Buffer.from([1]),
    keyFingerprint: 'SHA256:x',
    remotePathPrefix: '/home/u',
  });
}

describe('ssh/access resolveAccess', () => {
  let db: Database.Database;
  beforeEach(() => {
    db = bootstrapDb();
  });
  afterEach(() => {
    db.close();
    delete process.env.MCP_ENCRYPTION_KEY;
  });

  it('owner: allow via=owner', () => {
    const conn = mkConnection(db, 'alice');
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'alice',
      isAdmin: false,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(true);
    expect(r.via).toBe('owner');
  });

  it('admin with bypass enabled: allow via=admin', () => {
    const conn = mkConnection(db, 'alice');
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'admin',
      isAdmin: true,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(true);
    expect(r.via).toBe('admin');
  });

  it('admin without bypass: requires a grant', () => {
    const conn = mkConnection(db, 'alice');
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: false });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'admin',
      isAdmin: true,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(false);
    expect(r.reason).toBe('no_grant');
  });

  it('non-owner non-admin with active grant: allow via=grant', () => {
    const conn = mkConnection(db, 'alice');
    const grants = createGrantsRepo(db);
    grants.create({
      connectionId: conn.id,
      subjectType: 'user',
      subjectId: 'bob',
      pieceName: 'devops',
      appliesToAllPieces: false,
      grantedByUserId: 'admin',
      reason: 'on-call escalation',
    });
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'bob',
      isAdmin: false,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(true);
    expect(r.via).toBe('grant');
    expect(r.grant?.subjectId).toBe('bob');
  });

  it('non-owner non-admin without a grant: deny no_grant', () => {
    const conn = mkConnection(db, 'alice');
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'bob',
      isAdmin: false,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(false);
    expect(r.reason).toBe('no_grant');
  });

  it('disabled connection: deny even for owner', () => {
    const conn = mkConnection(db, 'alice');
    const repo = createConnectionRepo(db);
    repo.update(conn.id, { enabled: false });
    const refreshed = repo.resolveConnection(conn.id)!;
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: refreshed,
      userId: 'alice',
      isAdmin: false,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(false);
    expect(r.reason).toBe('disabled');
  });

  it('disabled connection: deny even for admin', () => {
    const conn = mkConnection(db, 'alice');
    const repo = createConnectionRepo(db);
    repo.disableByAdmin(conn.id, 'security incident', 'admin');
    const refreshed = repo.resolveConnection(conn.id)!;
    const grants = createGrantsRepo(db);
    const resolver = createAccessResolver(grants, { adminBypassesGrants: true });
    const r = resolver.resolveAccess({
      connection: refreshed,
      userId: 'admin',
      isAdmin: true,
      pieceName: 'devops',
      orgIds: [],
    });
    expect(r.allowed).toBe(false);
    expect(r.reason).toBe('disabled');
  });

  it('global connection (ownerId=null): non-owner, non-admin with grant works', () => {
    const conn = mkConnection(db, null);
    const grants = createGrantsRepo(db);
    grants.create({
      connectionId: conn.id,
      subjectType: 'org',
      subjectId: 'org-acme',
      pieceName: 'devops',
      appliesToAllPieces: false,
      grantedByUserId: 'admin',
      reason: 'team needs prod access',
    });
    const resolver = createAccessResolver(grants, { adminBypassesGrants: false });
    const r = resolver.resolveAccess({
      connection: conn,
      userId: 'bob',
      isAdmin: false,
      pieceName: 'devops',
      orgIds: ['org-acme'],
    });
    expect(r.allowed).toBe(true);
    expect(r.via).toBe('grant');
  });
});
