import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join, resolve } from 'path';
import { existsSync } from 'fs';
import { readFileSync } from 'fs';
import { EncryptedKeyStore } from './encryption.js';
import { generateEncryptionKey } from '../../utils/crypto.js';

let tmpDir: string;
let store: EncryptedKeyStore;
let masterKey: string;

beforeEach(() => {
  tmpDir = mkdtempSync(join(tmpdir(), 'enc-store-test-'));
  masterKey = generateEncryptionKey();
  store = new EncryptedKeyStore(tmpDir, masterKey);
});

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

describe('EncryptedKeyStore.init', () => {
  it('should create the config directory and store file', async () => {
    await store.init();
    const filePath = resolve(tmpDir, 'config', 'api-keys.enc.json');
    expect(existsSync(filePath)).toBe(true);
    const content = JSON.parse(readFileSync(filePath, 'utf-8'));
    expect(content).toEqual({ version: 1, keys: {} });
  });

  it('should not overwrite existing store on re-init', async () => {
    await store.init();
    await store.setKey('test-key', 'test-value');
    await store.init();
    const value = await store.getKey('test-key');
    expect(value).toBe('test-value');
  });
});

describe('EncryptedKeyStore.setKey / getKey', () => {
  it('should encrypt and store a key, then decrypt on retrieval', async () => {
    await store.init();
    await store.setKey('OPENROUTER_API_KEY', 'sk-or-v1-abc123');
    const value = await store.getKey('OPENROUTER_API_KEY');
    expect(value).toBe('sk-or-v1-abc123');
  });

  it('should store encrypted data, not plaintext', async () => {
    await store.init();
    await store.setKey('SECRET', 'my-secret-value');
    const filePath = resolve(tmpDir, 'config', 'api-keys.enc.json');
    const raw = readFileSync(filePath, 'utf-8');
    expect(raw).not.toContain('my-secret-value');
  });

  it('should return null for a non-existent key', async () => {
    await store.init();
    const value = await store.getKey('non-existent');
    expect(value).toBeNull();
  });

  it('should update an existing key and preserve createdAt', async () => {
    await store.init();
    await store.setKey('TOKEN', 'old-value');

    const filePath = resolve(tmpDir, 'config', 'api-keys.enc.json');
    const before = JSON.parse(readFileSync(filePath, 'utf-8'));
    const createdAt = before.keys['TOKEN'].createdAt;

    await store.setKey('TOKEN', 'new-value');
    const after = JSON.parse(readFileSync(filePath, 'utf-8'));
    expect(after.keys['TOKEN'].createdAt).toBe(createdAt);
    expect(after.keys['TOKEN'].updatedAt).not.toBe(createdAt);

    const value = await store.getKey('TOKEN');
    expect(value).toBe('new-value');
  });
});

describe('EncryptedKeyStore.deleteKey', () => {
  it('should delete an existing key and return true', async () => {
    await store.init();
    await store.setKey('TO_DELETE', 'value');
    const deleted = await store.deleteKey('TO_DELETE');
    expect(deleted).toBe(true);
    const value = await store.getKey('TO_DELETE');
    expect(value).toBeNull();
  });

  it('should return false when deleting a non-existent key', async () => {
    await store.init();
    const deleted = await store.deleteKey('nope');
    expect(deleted).toBe(false);
  });
});

describe('EncryptedKeyStore.listKeys', () => {
  it('should list keys with masked values showing last 4 chars', async () => {
    await store.init();
    await store.setKey('API_KEY', 'sk-or-v1-abcdef1234');
    await store.setKey('BOT_TOKEN', '123456:ABCdefGHIjklMNO');

    const keys = await store.listKeys();
    expect(keys).toHaveLength(2);

    const apiKey = keys.find(k => k.name === 'API_KEY');
    expect(apiKey).toBeDefined();
    expect(apiKey!.maskedValue).toBe('****1234');
    expect(apiKey!.createdAt).toBeTruthy();
    expect(apiKey!.updatedAt).toBeTruthy();

    const botToken = keys.find(k => k.name === 'BOT_TOKEN');
    expect(botToken).toBeDefined();
    expect(botToken!.maskedValue).toBe('****lMNO');
  });

  it('should mask short values as ****', async () => {
    await store.init();
    await store.setKey('SHORT', 'ab');
    const keys = await store.listKeys();
    const short = keys.find(k => k.name === 'SHORT');
    expect(short!.maskedValue).toBe('****');
  });

  it('should return empty array when no keys exist', async () => {
    await store.init();
    const keys = await store.listKeys();
    expect(keys).toEqual([]);
  });
});

describe('EncryptedKeyStore.hasKey', () => {
  it('should return true for existing key', async () => {
    await store.init();
    await store.setKey('EXISTS', 'value');
    expect(await store.hasKey('EXISTS')).toBe(true);
  });

  it('should return false for non-existent key', async () => {
    await store.init();
    expect(await store.hasKey('NOPE')).toBe(false);
  });
});

describe('EncryptedKeyStore error handling', () => {
  it('should throw when masterKey is empty on setKey', async () => {
    const noKeyStore = new EncryptedKeyStore(tmpDir, '');
    await noKeyStore.init();
    await expect(noKeyStore.setKey('k', 'v')).rejects.toThrow('ENCRYPTION_KEY is not configured');
  });

  it('should throw when masterKey is empty on getKey', async () => {
    const noKeyStore = new EncryptedKeyStore(tmpDir, '');
    await noKeyStore.init();
    await expect(noKeyStore.getKey('k')).rejects.toThrow('ENCRYPTION_KEY is not configured');
  });

  it('should throw when masterKey is empty on listKeys', async () => {
    const noKeyStore = new EncryptedKeyStore(tmpDir, '');
    await noKeyStore.init();
    await expect(noKeyStore.listKeys()).rejects.toThrow('ENCRYPTION_KEY is not configured');
  });

  it('should fail to decrypt with wrong master key', async () => {
    await store.init();
    await store.setKey('SECURE', 'secret-data');

    const wrongKeyStore = new EncryptedKeyStore(tmpDir, generateEncryptionKey());
    await expect(wrongKeyStore.getKey('SECURE')).rejects.toThrow();
  });
});
