import { beforeEach, describe, expect, it } from 'bun:test';
import { RedisCache } from '../src/redis-cache';
import type { CacheOptions } from '../src/types';

describe('RedisCache', () => {
  let cache: RedisCache;

  beforeEach(() => {
    const options: CacheOptions = {
      keyPrefix: 'test:',
      ttl: 3600,
      redisConfig: { host: 'localhost', port: 6379 },
    };
    cache = new RedisCache(options);
  });

  describe('Core functionality', () => {
    it('should create cache instance', () => {
      expect(cache).toBeDefined();
      expect(cache.isReady).toBeDefined();
      expect(cache.get).toBeDefined();
      expect(cache.set).toBeDefined();
    });

    it('should have stats tracking', () => {
      const stats = cache.getStats();
      expect(stats).toMatchObject({
        hits: 0,
        misses: 0,
        errors: 0,
        hitRate: 0,
        total: 0,
      });
      expect(stats.uptime).toBeGreaterThanOrEqual(0);
    });
  });

  describe('Basic operations', () => {
    it('should handle get/set operations', async () => {
      const key = 'test-key';
      const value = { foo: 'bar' };

      // Should return null for non-existent key
      const miss = await cache.get(key);
      expect(miss).toBeNull();

      // Should set and retrieve value
      await cache.set(key, value);
      const retrieved = await cache.get(key);
      expect(retrieved).toEqual(value);

      // Should delete key
      await cache.del(key);
      const deleted = await cache.get(key);
      expect(deleted).toBeNull();
    });

    it('should check key existence', async () => {
      const key = 'existence-test';

      expect(await cache.exists(key)).toBe(false);

      await cache.set(key, 'value');
      expect(await cache.exists(key)).toBe(true);

      await cache.del(key);
      expect(await cache.exists(key)).toBe(false);
    });

    it('should handle TTL in set operations', async () => {
      const key = 'ttl-test';
      const value = 'test-value';

      // Set with custom TTL as number
      await cache.set(key, value, 1);
      expect(await cache.get(key)).toBe(value);

      // Set with custom TTL in options
      await cache.set(key, value, { ttl: 2 });
      expect(await cache.get(key)).toBe(value);
    });
  });

  describe('Advanced set options', () => {
    it('should handle onlyIfExists option', async () => {
      const key = 'conditional-test';
      const value1 = 'value1';
      const value2 = 'value2';

      // Should not set if key doesn't exist
      await cache.set(key, value1, { onlyIfExists: true });
      expect(await cache.get(key)).toBeNull();

      // Create the key
      await cache.set(key, value1);

      // Should update if key exists
      await cache.set(key, value2, { onlyIfExists: true });
      expect(await cache.get(key)).toBe(value2);
    });

    it('should handle onlyIfNotExists option', async () => {
      const key = 'nx-test';
      const value1 = 'value1';
      const value2 = 'value2';

      // Should set if key doesn't exist
      await cache.set(key, value1, { onlyIfNotExists: true });
      expect(await cache.get(key)).toBe(value1);

      // Should not update if key exists
      await cache.set(key, value2, { onlyIfNotExists: true });
      expect(await cache.get(key)).toBe(value1);
    });

    it('should handle preserveTTL option', async () => {
      const key = 'preserve-ttl-test';
      const value1 = 'value1';
      const value2 = 'value2';

      // Set with short TTL
      await cache.set(key, value1, 10);

      // Update preserving TTL
      await cache.set(key, value2, { preserveTTL: true });
      expect(await cache.get(key)).toBe(value2);
    });

    it('should handle getOldValue option', async () => {
      const key = 'old-value-test';
      const value1 = 'value1';
      const value2 = 'value2';

      // Should return null when no old value
      const oldValue1 = await cache.set(key, value1, { getOldValue: true });
      expect(oldValue1).toBeNull();

      // Should return old value
      const oldValue2 = await cache.set(key, value2, { getOldValue: true });
      expect(oldValue2).toBe(value1);
    });
  });

  describe('Error handling', () => {
    it('should handle errors gracefully in get', async () => {
      // Force an error by using invalid JSON
      const badCache = new RedisCache({
        keyPrefix: 'bad:',
        redisConfig: { host: 'localhost', port: 6379 },
      });

      // This would normally throw but should return null
      const result = await badCache.get('non-existent');
      expect(result).toBeNull();

      // Check stats updated
      const stats = badCache.getStats();
      expect(stats.misses).toBe(1);
    });
  });

  describe('Pattern operations', () => {
    it('should find keys by pattern', async () => {
      // Clear first to ensure clean state
      await cache.clear();

      await cache.set('user:1', { id: 1 });
      await cache.set('user:2', { id: 2 });
      await cache.set('post:1', { id: 1 });

      const userKeys = await cache.keys('user:*');
      expect(userKeys).toHaveLength(2);
      expect(userKeys).toContain('user:1');
      expect(userKeys).toContain('user:2');

      const allKeys = await cache.keys('*');
      expect(allKeys.length).toBeGreaterThanOrEqual(3);
      expect(allKeys).toContain('user:1');
      expect(allKeys).toContain('user:2');
      expect(allKeys).toContain('post:1');
    });

    it('should clear all keys with prefix', async () => {
      await cache.set('key1', 'value1');
      await cache.set('key2', 'value2');

      await cache.clear();

      const keys = await cache.keys('*');
      expect(keys).toHaveLength(0);
    });
  });

  describe('Health checks', () => {
    it('should check health', async () => {
      const healthy = await cache.health();
      expect(healthy).toBe(true);
    });

    it('should check if ready', () => {
      // May not be ready immediately
      const ready = cache.isReady();
      expect(typeof ready).toBe('boolean');
    });

    it('should wait for ready', async () => {
      await expect(cache.waitForReady(1000)).resolves.toBeUndefined();
    });
  });
});
