/**
 * Timer Cleanup Tests for CorrelationCache
 *
 * Validates fix for timer leak vulnerability where interval timer
 * was not cleared when cache instance is destroyed.
 */

import { CorrelationCache } from '../src/lib/correlation-cache.js';

describe('CorrelationCache - Timer Cleanup', () => {
  let cache: CorrelationCache;

  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    if (cache && typeof (cache as any).destroy === 'function') {
      (cache as any).destroy();
    }
    vi.restoreAllMocks();
    vi.useRealTimers();
  });

  describe('destroy() method', () => {
    it('should have a destroy method', () => {
      cache = new CorrelationCache();
      expect(typeof (cache as any).destroy).toBe('function');
    });

    it('should clear interval timer when destroyed', () => {
      const clearIntervalSpy = vi.spyOn(global, 'clearInterval');

      cache = new CorrelationCache();
      (cache as any).destroy();

      expect(clearIntervalSpy).toHaveBeenCalled();
    });

    it('should handle multiple destroy calls safely', () => {
      cache = new CorrelationCache();

      (cache as any).destroy();
      expect(() => (cache as any).destroy()).not.toThrow();
    });

    it('should prevent cleanup from running after destroy', () => {
      cache = new CorrelationCache();

      // Add an entry
      cache.set('test:key1', { data: 'value' });

      // Destroy cache
      (cache as any).destroy();

      // Advance time past cleanup interval (60 seconds)
      vi.advanceTimersByTime(65 * 1000);

      // Entry should still exist (cleanup should not have run)
      expect(cache.has('test:key1')).toBe(true);
    });
  });

  describe('interval timer behavior', () => {
    it('should create interval timer on construction', () => {
      const setIntervalSpy = vi.spyOn(global, 'setInterval');

      cache = new CorrelationCache();

      expect(setIntervalSpy).toHaveBeenCalledWith(
        expect.any(Function),
        60 * 1000
      );
    });

    it('should clean up expired entries via interval', () => {
      cache = new CorrelationCache({ ttlMinutes: 1 });

      // Add entry
      cache.set('test:expires', { data: 'value' });
      expect(cache.has('test:expires')).toBe(true);

      // Advance time past TTL
      vi.advanceTimersByTime(61 * 1000);

      // Entry should be expired
      expect(cache.has('test:expires')).toBe(false);
    });

    it('should run cleanup periodically', () => {
      cache = new CorrelationCache({ ttlMinutes: 1 });

      // Add multiple entries at different times
      cache.set('test:key1', { data: 'value1' });

      vi.advanceTimersByTime(30 * 1000);
      cache.set('test:key2', { data: 'value2' });

      vi.advanceTimersByTime(30 * 1000);
      cache.set('test:key3', { data: 'value3' });

      // Advance to first cleanup (60s total)
      vi.advanceTimersByTime(5 * 1000);

      // key1 should be expired (65s old)
      expect(cache.has('test:key1')).toBe(false);
      // key2 and key3 should still exist
      expect(cache.has('test:key2')).toBe(true);
      expect(cache.has('test:key3')).toBe(true);
    });
  });

  describe('memory leak prevention', () => {
    it('should not leave dangling timers', () => {
      const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
      const setIntervalSpy = vi.spyOn(global, 'setInterval');

      // Create and destroy multiple cache instances
      for (let i = 0; i < 5; i++) {
        const tempCache = new CorrelationCache();
        (tempCache as any).destroy();
      }

      // Should have 5 setInterval calls and 5 clearInterval calls
      expect(setIntervalSpy).toHaveBeenCalledTimes(5);
      expect(clearIntervalSpy).toHaveBeenCalledTimes(5);
    });

    it('should clear interval ID after destroy', () => {
      cache = new CorrelationCache();

      // Interval ID should exist before destroy
      expect((cache as any).cleanupIntervalId).toBeDefined();

      (cache as any).destroy();

      // Interval ID should be undefined after destroy
      expect((cache as any).cleanupIntervalId).toBeUndefined();
    });
  });

  describe('integration with existing functionality', () => {
    it('should maintain normal cache operations before destroy', () => {
      cache = new CorrelationCache();

      cache.set('test:key1', { data: 'value1' });
      cache.set('test:key2', { data: 'value2' });

      expect(cache.get('test:key1')).toEqual({ data: 'value1' });
      expect(cache.get('test:key2')).toEqual({ data: 'value2' });

      const metrics = cache.getMetrics();
      expect(metrics.size).toBe(2);
      expect(metrics.hits).toBe(2);
    });

    it('should allow cache operations after destroy (graceful degradation)', () => {
      cache = new CorrelationCache();

      cache.set('test:key1', { data: 'value1' });
      (cache as any).destroy();

      // Cache should still work, just without automatic cleanup
      cache.set('test:key2', { data: 'value2' });
      expect(cache.get('test:key2')).toEqual({ data: 'value2' });
    });

    it('should preserve metrics after destroy', () => {
      cache = new CorrelationCache();

      cache.set('test:key1', { data: 'value1' });
      cache.get('test:key1');
      cache.get('test:nonexistent');

      (cache as any).destroy();

      const metrics = cache.getMetrics();
      expect(metrics.hits).toBe(1);
      expect(metrics.misses).toBe(1);
    });
  });

  describe('edge cases', () => {
    it('should handle destroy before any cache operations', () => {
      cache = new CorrelationCache();

      expect(() => (cache as any).destroy()).not.toThrow();

      // Should still be able to use cache
      cache.set('test:key', { data: 'value' });
      expect(cache.get('test:key')).toEqual({ data: 'value' });
    });

    it('should handle destroy with empty cache', () => {
      cache = new CorrelationCache();

      (cache as any).destroy();

      const metrics = cache.getMetrics();
      expect(metrics.size).toBe(0);
    });

    it('should handle destroy with full cache', () => {
      cache = new CorrelationCache({ maxSize: 10 });

      // Fill cache
      for (let i = 0; i < 10; i++) {
        cache.set(`test:key${i}`, { data: `value${i}` });
      }

      expect(() => (cache as any).destroy()).not.toThrow();
      expect(cache.getMetrics().size).toBe(10);
    });
  });
});
