/**
 * Response Cache Service Tests
 * Action 2.5.1.7: Test response cache works correctly
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { responseCache } from './responseCache';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';

describe('ResponseCacheService', () => {
  beforeEach(() => {
    // Clear cache before each test
    responseCache.clear();
  });

  describe('get', () => {
    it('should return null for non-GET requests', () => {
      const config: AxiosRequestConfig = {
        method: 'POST',
        url: '/api/v1/tracks',
      };

      const result = responseCache.get(config);
      expect(result).toBeNull();
    });

    it('should return null when cache is empty', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const result = responseCache.get(config);
      expect(result).toBeNull();
    });

    it('should return cached response for GET request', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config as any,
        request: {},
      };

      // Store response
      responseCache.set(config, response);

      // Retrieve cached response
      const cached = responseCache.get(config);

      expect(cached).not.toBeNull();
      expect(cached?.data).toEqual({ tracks: [] });
      expect(cached?.status).toBe(200);
    });

    it('should return null for expired cache entries', async () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: { 'cache-control': 'max-age=1' }, // 1 second TTL
        config: config as any,
        request: {},
      };

      // Store response
      responseCache.set(config, response);

      // Wait for cache to expire
      await new Promise((resolve) => setTimeout(resolve, 1100));

      // Should return null (expired)
      const cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should respect _disableCache flag', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
        _disableCache: true,
      } as any;

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config as any,
        request: {},
      };

      // Try to store (should be ignored)
      responseCache.set(config, response);

      // Should return null (caching disabled)
      const cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should handle different query parameters as different cache keys', () => {
      const config1: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
        params: { page: 1 },
      };

      const config2: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
        params: { page: 2 },
      };

      const response1: AxiosResponse = {
        data: { tracks: [], page: 1 },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config1 as any,
        request: {},
      };

      const response2: AxiosResponse = {
        data: { tracks: [], page: 2 },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config2 as any,
        request: {},
      };

      responseCache.set(config1, response1);
      responseCache.set(config2, response2);

      const cached1 = responseCache.get(config1);
      const cached2 = responseCache.get(config2);

      expect(cached1?.data.page).toBe(1);
      expect(cached2?.data.page).toBe(2);
    });
  });

  describe('set', () => {
    it('should cache GET request responses', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const cached = responseCache.get(config);
      expect(cached).not.toBeNull();
      expect(cached?.data).toEqual({ tracks: [] });
    });

    it('should not cache non-GET requests', () => {
      const config: AxiosRequestConfig = {
        method: 'POST',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { id: '123' },
        status: 201,
        statusText: 'Created',
        headers: {},
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should respect Cache-Control no-store directive', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: { 'cache-control': 'no-store' },
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should respect Cache-Control no-cache directive', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: { 'cache-control': 'no-cache' },
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should respect Cache-Control max-age directive', async () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: { 'cache-control': 'max-age=1' }, // 1 second
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      // Should be cached immediately
      let cached = responseCache.get(config);
      expect(cached).not.toBeNull();

      // Wait for expiration
      await new Promise((resolve) => setTimeout(resolve, 1100));

      // Should be expired
      cached = responseCache.get(config);
      expect(cached).toBeNull();
    });

    it('should store ETag and Last-Modified headers', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {
          etag: '"abc123"',
          'last-modified': 'Wed, 21 Oct 2015 07:28:00 GMT',
        },
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const cached = responseCache.get(config);
      expect(cached).not.toBeNull();
      // Note: ETag and Last-Modified are stored internally but not exposed in get() return
    });
  });

  describe('invalidate', () => {
    it('should invalidate cache entries matching string pattern', () => {
      const config1: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const config2: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/playlists',
      };

      const response1: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config1 as any,
        request: {},
      };

      const response2: AxiosResponse = {
        data: { playlists: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config2 as any,
        request: {},
      };

      responseCache.set(config1, response1);
      responseCache.set(config2, response2);

      // Invalidate tracks pattern
      const invalidated = responseCache.invalidate('/tracks');

      expect(invalidated).toBeGreaterThan(0);
      expect(responseCache.get(config1)).toBeNull();
      expect(responseCache.get(config2)).not.toBeNull();
    });

    it('should invalidate cache entries matching regex pattern', () => {
      const config1: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks/123',
      };

      const config2: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks/456',
      };

      const response1: AxiosResponse = {
        data: { id: '123' },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config1 as any,
        request: {},
      };

      const response2: AxiosResponse = {
        data: { id: '456' },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config2 as any,
        request: {},
      };

      responseCache.set(config1, response1);
      responseCache.set(config2, response2);

      // Invalidate all tracks/* entries
      const invalidated = responseCache.invalidate(/\/tracks\/\d+/);

      expect(invalidated).toBeGreaterThan(0);
      expect(responseCache.get(config1)).toBeNull();
      expect(responseCache.get(config2)).toBeNull();
    });
  });

  describe('clear', () => {
    it('should clear all cache entries', () => {
      const config1: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const config2: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/playlists',
      };

      const response1: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config1 as any,
        request: {},
      };

      const response2: AxiosResponse = {
        data: { playlists: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config2 as any,
        request: {},
      };

      responseCache.set(config1, response1);
      responseCache.set(config2, response2);

      responseCache.clear();

      expect(responseCache.get(config1)).toBeNull();
      expect(responseCache.get(config2)).toBeNull();
      expect(responseCache.getStats().size).toBe(0);
    });
  });

  describe('getStats', () => {
    it('should return cache statistics', () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      const stats = responseCache.getStats();

      expect(stats.size).toBeGreaterThan(0);
      expect(stats.maxSize).toBe(100);
      expect(stats.entries).toBeDefined();
      expect(stats.entries.length).toBeGreaterThan(0);
      expect(stats.entries[0]).toHaveProperty('key');
      expect(stats.entries[0]).toHaveProperty('age');
    });
  });

  describe('cleanup', () => {
    it('should remove expired cache entries', async () => {
      const config: AxiosRequestConfig = {
        method: 'GET',
        url: '/api/v1/tracks',
      };

      const response: AxiosResponse = {
        data: { tracks: [] },
        status: 200,
        statusText: 'OK',
        headers: { 'cache-control': 'max-age=1' }, // 1 second TTL
        config: config as any,
        request: {},
      };

      responseCache.set(config, response);

      // Wait for expiration
      await new Promise((resolve) => setTimeout(resolve, 1100));

      // Cleanup should remove expired entries
      const removed = responseCache.cleanup();

      expect(removed).toBeGreaterThan(0);
      expect(responseCache.get(config)).toBeNull();
    });
  });

  describe('cache size limits', () => {
    it('should evict oldest entries when cache size limit is reached', () => {
      // Create more than maxSize (100) entries
      const entries: Array<{
        config: AxiosRequestConfig;
        response: AxiosResponse;
      }> = [];

      for (let i = 0; i < 105; i++) {
        const config: AxiosRequestConfig = {
          method: 'GET',
          url: `/api/v1/tracks/${i}`,
        };

        const response: AxiosResponse = {
          data: { id: i },
          status: 200,
          statusText: 'OK',
          headers: {},
          config: config as any,
          request: {},
        };

        entries.push({ config, response });
        responseCache.set(config, response);
      }

      // Cache size should be limited to maxSize
      const stats = responseCache.getStats();
      expect(stats.size).toBeLessThanOrEqual(100);

      // Oldest entries should be evicted
      // First few entries should be missing
      expect(responseCache.get(entries[0].config)).toBeNull();
    });
  });
});
