/**
 * Repository tests for AAO Gateway Phase 2a virtual keys.
 *
 * Coverage targets:
 *  - create / findByHash / findById / list
 *  - allowedModels JSON round-trip (incl. null vs [] vs ['a','b'])
 *  - source defaulting + persistence
 *  - revoke idempotency + activeOnly filter
 *  - UNIQUE(key_hash) constraint
 *  - touch updates last_used_at
 *  - delete returns boolean and removes the row
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Repository } from './repository.js';

describe('Repository gateway_virtual_keys (Phase 2a)', () => {
  let tmpDir: string;
  let repo: Repository;

  beforeEach(() => {
    tmpDir = mkdtempSync(join(tmpdir(), 'gw-keys-repo-test-'));
    repo = new Repository(join(tmpDir, 'test.db'));
  });

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

  it('creates a key with admin defaults and is retrievable by hash', () => {
    const created = repo.createGatewayVirtualKey({
      keyHash: 'hash-1',
      keyPrefix: 'sk-aao-AAAAAA',
      team: 'alpha',
    });
    expect(created.id).toBeTruthy();
    expect(created.source).toBe('admin');
    expect(created.allowedModels).toBeNull();
    expect(created.revokedAt).toBeNull();

    const found = repo.findGatewayVirtualKeyByHash('hash-1');
    expect(found?.id).toBe(created.id);
    expect(found?.team).toBe('alpha');
  });

  it('persists allowed_models JSON and distinguishes null vs empty array', () => {
    const withList = repo.createGatewayVirtualKey({
      keyHash: 'hash-list',
      keyPrefix: 'sk-aao-LIST',
      team: 't1',
      allowedModels: ['qwen3:8b', 'qwen3:14b'],
    });
    expect(withList.allowedModels).toEqual(['qwen3:8b', 'qwen3:14b']);
    const empty = repo.createGatewayVirtualKey({
      keyHash: 'hash-empty',
      keyPrefix: 'sk-aao-EMPTY',
      team: 't1',
      allowedModels: [],
    });
    expect(empty.allowedModels).toEqual([]);
    const explicitNull = repo.createGatewayVirtualKey({
      keyHash: 'hash-null',
      keyPrefix: 'sk-aao-NULL0',
      team: 't1',
      allowedModels: null,
    });
    expect(explicitNull.allowedModels).toBeNull();
  });

  it('records the source value verbatim (config-import vs admin)', () => {
    const cfg = repo.createGatewayVirtualKey({
      keyHash: 'h-cfg',
      keyPrefix: 'sk-aao-CFGCFG',
      team: 'imported',
      source: 'config-import',
      createdBy: 'config',
    });
    expect(cfg.source).toBe('config-import');
    expect(cfg.createdBy).toBe('config');
  });

  it('rejects duplicate key_hash via UNIQUE constraint', () => {
    repo.createGatewayVirtualKey({
      keyHash: 'dup-hash',
      keyPrefix: 'sk-aao-DUPDUP',
      team: 'alpha',
    });
    expect(() =>
      repo.createGatewayVirtualKey({
        keyHash: 'dup-hash',
        keyPrefix: 'sk-aao-OTHER',
        team: 'beta',
      }),
    ).toThrow(/UNIQUE/);
  });

  it('hides revoked keys from findGatewayVirtualKeyByHash', () => {
    const k = repo.createGatewayVirtualKey({
      keyHash: 'will-revoke',
      keyPrefix: 'sk-aao-REVOKE',
      team: 'alpha',
    });
    expect(repo.findGatewayVirtualKeyByHash('will-revoke')).not.toBeNull();
    const ok = repo.revokeGatewayVirtualKey(k.id, 'admin-user');
    expect(ok).toBe(true);
    expect(repo.findGatewayVirtualKeyByHash('will-revoke')).toBeNull();
    // Second revoke is a no-op.
    expect(repo.revokeGatewayVirtualKey(k.id, 'admin-user')).toBe(false);
    // But findById still exposes it for auditing.
    const audited = repo.findGatewayVirtualKeyById(k.id);
    expect(audited?.revokedAt).toBeTruthy();
    expect(audited?.revokedBy).toBe('admin-user');
  });

  it('lists with team + activeOnly filters', () => {
    const a = repo.createGatewayVirtualKey({
      keyHash: 'h-a',
      keyPrefix: 'sk-aao-AAAAA1',
      team: 'alpha',
    });
    repo.createGatewayVirtualKey({
      keyHash: 'h-b',
      keyPrefix: 'sk-aao-BBBBBB',
      team: 'beta',
    });
    const revoked = repo.createGatewayVirtualKey({
      keyHash: 'h-a-old',
      keyPrefix: 'sk-aao-OLD000',
      team: 'alpha',
    });
    repo.revokeGatewayVirtualKey(revoked.id, 'admin');

    const allAlpha = repo.listGatewayVirtualKeys({ team: 'alpha' });
    expect(allAlpha).toHaveLength(2);
    const activeAlpha = repo.listGatewayVirtualKeys({ team: 'alpha', activeOnly: true });
    expect(activeAlpha).toHaveLength(1);
    expect(activeAlpha[0]!.id).toBe(a.id);
  });

  it('touches last_used_at without changing other columns', () => {
    const k = repo.createGatewayVirtualKey({
      keyHash: 'h-touch',
      keyPrefix: 'sk-aao-TOUCH0',
      team: 'alpha',
    });
    expect(k.lastUsedAt).toBeNull();
    repo.touchGatewayVirtualKeyLastUsed(k.id, '2026-05-19T12:00:00.000Z');
    const after = repo.findGatewayVirtualKeyById(k.id);
    expect(after?.lastUsedAt).toBe('2026-05-19T12:00:00.000Z');
    expect(after?.team).toBe('alpha');
    expect(after?.revokedAt).toBeNull();
  });

  it('delete returns true on hit and false on miss', () => {
    const k = repo.createGatewayVirtualKey({
      keyHash: 'h-delete',
      keyPrefix: 'sk-aao-DELETE',
      team: 'alpha',
    });
    expect(repo.deleteGatewayVirtualKey(k.id)).toBe(true);
    expect(repo.findGatewayVirtualKeyById(k.id)).toBeNull();
    expect(repo.deleteGatewayVirtualKey(k.id)).toBe(false);
  });

  it('refuses to delete a config-import key (defense-in-depth)', () => {
    // The admin REST API also rejects this, but the Repository must
    // refuse too: a future internal caller could otherwise hard-delete
    // a config-import row that would just be replayed on next gateway
    // boot when importConfigKeysToDb re-imports it with a different id.
    const cfg = repo.createGatewayVirtualKey({
      keyHash: 'cfg-protect',
      keyPrefix: 'sk-aao-CFGGGG',
      team: 'imported',
      source: 'config-import',
      createdBy: 'config',
    });
    expect(() => repo.deleteGatewayVirtualKey(cfg.id)).toThrow(/config-import/i);
    // Row must still be present.
    expect(repo.findGatewayVirtualKeyById(cfg.id)).not.toBeNull();
  });

  it('still deletes admin-issued keys after the source check is in place', () => {
    const admin = repo.createGatewayVirtualKey({
      keyHash: 'admin-key',
      keyPrefix: 'sk-aao-ADMIN0',
      team: 'team1',
      source: 'admin',
    });
    expect(repo.deleteGatewayVirtualKey(admin.id)).toBe(true);
    expect(repo.findGatewayVirtualKeyById(admin.id)).toBeNull();
  });
});
