import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { CacheFactory, createNamespacedCache } from '../src/cache-factory';
import { generateKey } from '../src/key-generator';
import type { ICache } from '../src/types';

describe('CacheFactory', () => {
  it('should create null cache when no config provided', () => {
    const cache = CacheFactory.create(null as any, 'test');
    expect(cache).toBeDefined();
    expect(cache.type).toBe('null');
  });

  it('should create cache with namespace', () => {
    const mockConfig = {
      cache: {
        provider: 'memory',
        redis: { host: 'localhost', port: 6379 },
      },
    };
    const cache = CacheFactory.create(mockConfig as any, 'test-namespace');
    expect(cache).toBeDefined();
  });
});

describe('NamespacedCache', () => {
  let mockCache: ICache;

  beforeEach(() => {
    mockCache = {
      type: 'mock',
      get: mock(() => Promise.resolve(null)),
      set: mock(() => Promise.resolve()),
      del: mock(() => Promise.resolve()),
      clear: mock(() => Promise.resolve()),
      exists: mock(() => Promise.resolve(false)),
      ttl: mock(() => Promise.resolve(-1)),
      keys: mock(() => Promise.resolve([])),
      mget: mock(() => Promise.resolve([])),
      mset: mock(() => Promise.resolve()),
      mdel: mock(() => Promise.resolve()),
      size: mock(() => Promise.resolve(0)),
      flush: mock(() => Promise.resolve()),
      ping: mock(() => Promise.resolve(true)),
      disconnect: mock(() => Promise.resolve()),
      isConnected: mock(() => true),
    };
  });

  it('should create namespaced cache', () => {
    const nsCache = createNamespacedCache(mockCache, 'sub-namespace');
    expect(nsCache).toBeDefined();
    expect(nsCache).toHaveProperty('get');
    expect(nsCache).toHaveProperty('set');
  });

  it('should prefix keys with namespace', async () => {
    const nsCache = createNamespacedCache(mockCache, 'test');
    await nsCache.set('key', 'value');
    expect(mockCache.set).toHaveBeenCalledWith('test:key', 'value', undefined);
  });

  it('should handle null cache gracefully', async () => {
    const nsCache = createNamespacedCache(null, 'test');
    expect(nsCache).toBeDefined();
    // Should return null for get operations
    const value = await nsCache.get('key');
    expect(value).toBeNull();
  });

  it('should prefix multiple operations', async () => {
    const nsCache = createNamespacedCache(mockCache, 'prefix');

    await nsCache.get('key1');
    expect(mockCache.get).toHaveBeenCalledWith('prefix:key1');

    await nsCache.del('key2');
    expect(mockCache.del).toHaveBeenCalledWith('prefix:key2');

    await nsCache.exists('key3');
    expect(mockCache.exists).toHaveBeenCalledWith('prefix:key3');
  });

  it('should handle pattern operations', async () => {
    const nsCache = createNamespacedCache(mockCache, 'ns');
    mockCache.keys = mock(() => Promise.resolve(['ns:key1', 'ns:key2', 'other:key']));

    const keys = await nsCache.keys('*');
    expect(mockCache.keys).toHaveBeenCalledWith('ns:*');
    expect(keys).toEqual(['key1', 'key2']);
  });
});

describe('KeyGenerator', () => {
  it('should generate key from parts', () => {
    const key = generateKey('part1', 'part2', 'part3');
    expect(key).toBe('part1:part2:part3');
  });

  it('should handle empty parts', () => {
    const key = generateKey();
    expect(key).toBe('');
  });

  it('should skip undefined parts', () => {
    const key = generateKey('part1', undefined, 'part3');
    expect(key).toBe('part1:part3');
  });

  it('should convert non-string parts', () => {
    const key = generateKey('prefix', 123, true);
    expect(key).toBe('prefix:123:true');
  });
});
