import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { cache } from './cache';

describe('Cache', () => {
  beforeEach(() => {
    vi.useFakeTimers();
    cache.clear(); // Clear cache before each test
  });

  afterEach(() => {
    vi.useRealTimers();
    cache.clear(); // Clean up after each test
  });

  describe('set and get', () => {
    it('should set and retrieve a value', () => {
      cache.set('key', 'value');
      expect(cache.get('key')).toBe('value');
    });

    it('should set multiple values with different keys', () => {
      cache.set('key1', 'value1');
      cache.set('key2', 'value2');
      cache.set('key3', 'value3');

      expect(cache.get('key1')).toBe('value1');
      expect(cache.get('key2')).toBe('value2');
      expect(cache.get('key3')).toBe('value3');
    });

    it('should overwrite existing value for same key', () => {
      cache.set('key', 'value1');
      expect(cache.get('key')).toBe('value1');

      cache.set('key', 'value2');
      expect(cache.get('key')).toBe('value2');
    });

    it('should return null for non-existing key', () => {
      expect(cache.get('nonexistent')).toBeNull();
    });

    it('should store different types of values', () => {
      cache.set('string', 'text');
      cache.set('number', 42);
      cache.set('boolean', true);
      cache.set('object', { name: 'test' });
      cache.set('array', [1, 2, 3]);
      cache.set('null', null);

      expect(cache.get('string')).toBe('text');
      expect(cache.get('number')).toBe(42);
      expect(cache.get('boolean')).toBe(true);
      expect(cache.get('object')).toEqual({ name: 'test' });
      expect(cache.get('array')).toEqual([1, 2, 3]);
      expect(cache.get('null')).toBeNull();
    });
  });

  describe('TTL expiration', () => {
    it('should use default TTL of 5 minutes', () => {
      cache.set('test', 'value');

      // Should be available before TTL expires
      expect(cache.get('test')).toBe('value');

      // Advance time by 4 minutes 59 seconds
      vi.advanceTimersByTime(4 * 60 * 1000 + 59 * 1000);
      expect(cache.get('test')).toBe('value');

      // Advance time by 2 more seconds (total 5 minutes 1 second)
      vi.advanceTimersByTime(2 * 1000);
      expect(cache.get('test')).toBeNull();
    });

    it('should accept custom TTL per item', () => {
      cache.set('short', 'value', 1000); // 1 second TTL
      cache.set('long', 'value', 10000); // 10 seconds TTL

      // Both should be available initially
      expect(cache.get('short')).toBe('value');
      expect(cache.get('long')).toBe('value');

      // After 1.5 seconds, short should expire but long should remain
      vi.advanceTimersByTime(1500);
      expect(cache.get('short')).toBeNull();
      expect(cache.get('long')).toBe('value');

      // After 9 more seconds (total 10.5), long should expire
      vi.advanceTimersByTime(9000);
      expect(cache.get('long')).toBeNull();
    });

    it('should return null for expired item', () => {
      cache.set('key', 'value', 1000);

      // Should be available initially
      expect(cache.get('key')).toBe('value');

      // Should be null after expiration
      vi.advanceTimersByTime(1001);
      expect(cache.get('key')).toBeNull();
    });

    it('should delete expired item from storage', () => {
      cache.set('key', 'value', 1000);

      // Item should exist
      expect(cache.get('key')).toBe('value');

      // After expiration, get should delete it
      vi.advanceTimersByTime(1001);
      expect(cache.get('key')).toBeNull();

      // Subsequent get should also return null (item was deleted)
      expect(cache.get('key')).toBeNull();
    });

    it('should return value at exact TTL boundary', () => {
      cache.set('key', 'value', 1000);

      // At exactly TTL time, should still be valid
      vi.advanceTimersByTime(1000);
      expect(cache.get('key')).toBe('value');

      // One millisecond later, should expire
      vi.advanceTimersByTime(1);
      expect(cache.get('key')).toBeNull();
    });

    it('should respect different TTLs for different items', () => {
      cache.set('item1', 'value1', 1000);
      cache.set('item2', 'value2', 2000);
      cache.set('item3', 'value3', 3000);

      // All items should be available initially
      expect(cache.get('item1')).toBe('value1');
      expect(cache.get('item2')).toBe('value2');
      expect(cache.get('item3')).toBe('value3');

      // After 1.5 seconds, item1 should expire
      vi.advanceTimersByTime(1500);
      expect(cache.get('item1')).toBeNull();
      expect(cache.get('item2')).toBe('value2');
      expect(cache.get('item3')).toBe('value3');

      // After 1 more second (total 2.5), item2 should expire
      vi.advanceTimersByTime(1000);
      expect(cache.get('item1')).toBeNull();
      expect(cache.get('item2')).toBeNull();
      expect(cache.get('item3')).toBe('value3');

      // After 1 more second (total 3.5), item3 should expire
      vi.advanceTimersByTime(1000);
      expect(cache.get('item1')).toBeNull();
      expect(cache.get('item2')).toBeNull();
      expect(cache.get('item3')).toBeNull();
    });

    it('should reset TTL when item is overwritten', () => {
      cache.set('key', 'value1', 1000);

      // Advance time by 500ms
      vi.advanceTimersByTime(500);

      // Overwrite with new value and new TTL
      cache.set('key', 'value2', 2000);

      // After 1500ms more (total 2000ms from first set), should still be available
      // because TTL was reset
      vi.advanceTimersByTime(1500);
      expect(cache.get('key')).toBe('value2');

      // After 600ms more (2100ms from second set), should expire
      vi.advanceTimersByTime(600);
      expect(cache.get('key')).toBeNull();
    });

    it('should handle zero TTL', () => {
      cache.set('key', 'value', 0);

      // Should expire immediately
      vi.advanceTimersByTime(1);
      expect(cache.get('key')).toBeNull();
    });

    it('should handle very long TTL', () => {
      const oneDayInMs = 24 * 60 * 60 * 1000;
      cache.set('key', 'value', oneDayInMs);

      // Should be available after 23 hours
      vi.advanceTimersByTime(23 * 60 * 60 * 1000);
      expect(cache.get('key')).toBe('value');

      // Should still be available at exactly 24 hours
      vi.advanceTimersByTime(60 * 60 * 1000);
      expect(cache.get('key')).toBe('value');

      // Should expire after 24 hours + 1ms
      vi.advanceTimersByTime(1);
      expect(cache.get('key')).toBeNull();
    });
  });

  describe('has', () => {
    it('should return true for existing key', () => {
      cache.set('key', 'value');
      expect(cache.has('key')).toBe(true);
    });

    it('should return false for non-existing key', () => {
      expect(cache.has('nonexistent')).toBe(false);
    });

    it('should return false for expired key', () => {
      cache.set('key', 'value', 1000);

      expect(cache.has('key')).toBe(true);

      vi.advanceTimersByTime(1001);
      expect(cache.has('key')).toBe(false);
    });

    it('should return false for null values', () => {
      cache.set('key', null);
      expect(cache.has('key')).toBe(false);
    });
  });

  describe('delete', () => {
    it('should delete existing key', () => {
      cache.set('key', 'value');
      expect(cache.get('key')).toBe('value');

      cache.delete('key');
      expect(cache.get('key')).toBeNull();
    });

    it('should handle deleting non-existing key', () => {
      expect(() => cache.delete('nonexistent')).not.toThrow();
      expect(cache.get('nonexistent')).toBeNull();
    });

    it('should only delete specified key', () => {
      cache.set('key1', 'value1');
      cache.set('key2', 'value2');
      cache.set('key3', 'value3');

      cache.delete('key2');

      expect(cache.get('key1')).toBe('value1');
      expect(cache.get('key2')).toBeNull();
      expect(cache.get('key3')).toBe('value3');
    });
  });

  describe('clear', () => {
    it('should clear all items from cache', () => {
      cache.set('key1', 'value1');
      cache.set('key2', 'value2');
      cache.set('key3', 'value3');

      cache.clear();

      expect(cache.get('key1')).toBeNull();
      expect(cache.get('key2')).toBeNull();
      expect(cache.get('key3')).toBeNull();
    });

    it('should handle clearing empty cache', () => {
      expect(() => cache.clear()).not.toThrow();
    });

    it('should allow setting items after clear', () => {
      cache.set('key', 'value1');
      cache.clear();
      cache.set('key', 'value2');

      expect(cache.get('key')).toBe('value2');
    });
  });
});
