import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { connectDB, get, set, del, has, clear, getDB, asyncKV } from './kv';

beforeEach(() => {
  // Use in-memory database for tests
  connectDB(':memory:');
});

afterEach(() => {
  // Clear the database after each test
  clear();
});

describe('KV Store Tests', () => {
  test('set and get string value', async () => {
    await set('testKey', 'testValue');
    const value = await get<string>('testKey');
    expect(value).toBe('testValue');
  });

  test('set and get object value', async () => {
    const obj = { name: 'test', value: 42 };
    await set('testKey', obj);
    const value = await get<typeof obj>('testKey');
    expect(value).toEqual(obj);
  });

  test('set and get boolean value', async () => {
    await set('testKey', true);
    const value = await get<boolean>('testKey');
    expect(value).toBe(true);
  });

  test('set and get null value', async () => {
    await set('testKey', null);
    const value = await get<null>('testKey');
    expect(value).toBe(null);
  });

  test('get non-existent key returns undefined', async () => {
    const value = await get('nonExistent');
    expect(value).toBeUndefined();
  });

  test('set with TTL and get before expiration', async () => {
    await set('testKey', 'testValue', 1); // 1 second TTL
    const value = await get<string>('testKey');
    expect(value).toBe('testValue');
  });

  test('set with TTL and get after expiration', async () => {
    await set('testKey', 'testValue', 0.001); // Very short TTL
    await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for expiration
    const value = await get<string>('testKey');
    expect(value).toBeUndefined();
  });

  test('del existing key', async () => {
    await set('testKey', 'testValue');
    const deleted = await del('testKey');
    expect(deleted).toBe(1); // Number of rows deleted
    const value = await get('testKey');
    expect(value).toBeUndefined();
  });

  test('del non-existent key', async () => {
    const deleted = await del('nonExistent');
    expect(deleted).toBe(0); // del returns 0 for non-existent keys
  });

  test('del multiple keys', async () => {
    await set('key1', 'value1');
    await set('key2', 'value2');
    await set('key3', 'value3');
    const deleted = await del(['key1', 'key2', 'nonExistent']);
    expect(deleted).toBe(2); // key1 and key2 deleted, nonExistent not
    expect(await get('key1')).toBeUndefined();
    expect(await get('key2')).toBeUndefined();
    expect(await get('key3')).toBeDefined(); // key3 still exists
  });

  test('has existing key', async () => {
    await set('testKey', 'testValue');
    const exists = await has('testKey');
    expect(exists).toBe(true);
  });

  test('has non-existent key', async () => {
    const exists = await has('nonExistent');
    expect(exists).toBe(false);
  });

  test('has key with expired TTL', async () => {
    await set('testKey', 'testValue', 0.001);
    await new Promise((resolve) => setTimeout(resolve, 10));
    const exists = await has('testKey');
    expect(exists).toBe(false);
  });

  test('clear all keys', async () => {
    await set('key1', 'value1');
    await set('key2', 'value2');
    await clear();
    const value1 = await get('key1');
    const value2 = await get('key2');
    expect(value1).toBeUndefined();
    expect(value2).toBeUndefined();
  });

  test('getDB returns database instance', () => {
    const db = getDB();
    expect(db).toBeDefined();
    expect(db.constructor.name).toBe('Database');
  });

  test('connectDB with custom path', () => {
    connectDB(':memory:'); // Already done in beforeEach, but testing again
    const db = getDB();
    expect(db).toBeDefined();
  });

  test('set and get zero value', async () => {
    await set('testKey', 0);
    const value = await get<number>('testKey');
    expect(value).toBe(0);
  });

  test('set and get empty string', async () => {
    await set('testKey', '');
    const value = await get<string>('testKey');
    expect(value).toBe('');
  });

  test('set with false value', async () => {
    await set('testKey', false);
    const value = await get<boolean>('testKey');
    expect(value).toBe(false);
  });

  test('JSON parsing error falls back to string', async () => {
    // Manually insert invalid JSON to test fallback
    const db = getDB();
    db.run(`INSERT OR REPLACE INTO kvstore VALUES (?, ?, ?, ?)`, ['testKey', 'invalid json', null, new Date().toISOString()]);
    const value = await get<string>('testKey');
    expect(value).toBe('invalid json');
  });

  // Tests for asyncKV wrapper functions
  test('asyncKV set and get', async () => {
    await asyncKV.set('asyncKey', 'asyncValue');
    const value = await asyncKV.get<string>('asyncKey');
    expect(value).toBe('asyncValue');
  });

  test('asyncKV del', async () => {
    await asyncKV.set('asyncKey', 'asyncValue');
    const deleted = await asyncKV.del('asyncKey');
    expect(deleted).toBe(1);
    const value = await asyncKV.get('asyncKey');
    expect(value).toBeUndefined();
  });

  test('asyncKV has', async () => {
    await asyncKV.set('asyncKey', 'asyncValue');
    const exists = await asyncKV.has('asyncKey');
    expect(exists).toBe(true);
    await asyncKV.del('asyncKey');
    const existsAfter = await asyncKV.has('asyncKey');
    expect(existsAfter).toBe(false);
  });

  test('asyncKV clear', async () => {
    await asyncKV.set('key1', 'value1');
    await asyncKV.set('key2', 'value2');
    await asyncKV.clear();
    const value1 = await asyncKV.get('key1');
    const value2 = await asyncKV.get('key2');
    expect(value1).toBeUndefined();
    expect(value2).toBeUndefined();
  });

  test('asyncKV del multiple keys', async () => {
    await asyncKV.set('key1', 'value1');
    await asyncKV.set('key2', 'value2');
    const deleted = await asyncKV.del(['key1', 'key2']);
    expect(deleted).toBe(2);
  });
});
