/**
 * Tests for CacheManager
 */

import { CacheManager, CachePresets } from "../CacheManager.js";

describe("CacheManager", () => {
  let cacheManager: CacheManager;

  beforeEach(() => {
    cacheManager = new CacheManager({
      maxSize: 100,
      defaultTTL: 1000,
      enableLRU: true,
      enableStats: true,
    });
  });

  afterEach(() => {
    cacheManager.clear();
  });

  describe("Basic Operations", () => {
    test("should store and retrieve values", () => {
      const key = "test-key";
      const value = { data: "test value" };

      cacheManager.set(key, value);
      const retrieved = cacheManager.get(key);

      expect(retrieved).toEqual(value);
    });

    test("should return null for non-existent keys", () => {
      const result = cacheManager.get("non-existent");
      expect(result).toBeNull();
    });

    test("should check if key exists", () => {
      const key = "exists-test";
      expect(cacheManager.has(key)).toBe(false);

      cacheManager.set(key, "value");
      expect(cacheManager.has(key)).toBe(true);
    });

    test("should delete entries", () => {
      const key = "delete-test";
      cacheManager.set(key, "value");

      expect(cacheManager.has(key)).toBe(true);
      const deleted = cacheManager.delete(key);

      expect(deleted).toBe(true);
      expect(cacheManager.has(key)).toBe(false);
    });

    test("should clear all entries", () => {
      cacheManager.set("key1", "value1");
      cacheManager.set("key2", "value2");

      expect(cacheManager.getStats().totalSize).toBe(2);

      cacheManager.clear();

      expect(cacheManager.getStats().totalSize).toBe(0);
      expect(cacheManager.has("key1")).toBe(false);
      expect(cacheManager.has("key2")).toBe(false);
    });
  });

  describe("TTL and Expiration", () => {
    test("should expire entries after TTL", async () => {
      const key = "ttl-test";
      const shortTTL = 50; // 50ms

      cacheManager.set(key, "value", shortTTL);
      expect(cacheManager.get(key)).toBe("value");

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

      expect(cacheManager.get(key)).toBeNull();
    });

    test("should not expire entries before TTL", () => {
      const key = "no-expire-test";
      const longTTL = 10000; // 10 seconds

      cacheManager.set(key, "value", longTTL);
      expect(cacheManager.get(key)).toBe("value");
    });

    test("should use default TTL when not specified", () => {
      const key = "default-ttl-test";
      cacheManager.set(key, "value");

      const entry = cacheManager.getEntry(key);
      expect(entry?.ttl).toBe(1000);
    });
  });

  describe("Key Generation", () => {
    test("should generate consistent keys for same parameters", () => {
      const siteId = "site1";
      const endpoint = "posts";
      const params = { per_page: 10, status: "publish" };

      const key1 = cacheManager.generateKey(siteId, endpoint, params);
      const key2 = cacheManager.generateKey(siteId, endpoint, params);

      expect(key1).toBe(key2);
    });

    test("should generate different keys for different parameters", () => {
      const siteId = "site1";
      const endpoint = "posts";
      const params1 = { per_page: 10 };
      const params2 = { per_page: 20 };

      const key1 = cacheManager.generateKey(siteId, endpoint, params1);
      const key2 = cacheManager.generateKey(siteId, endpoint, params2);

      expect(key1).not.toBe(key2);
    });

    test("should include site ID in key", () => {
      const endpoint = "posts";
      const params = { per_page: 10 };

      const key1 = cacheManager.generateKey("site1", endpoint, params);
      const key2 = cacheManager.generateKey("site2", endpoint, params);

      expect(key1).toContain("site1:");
      expect(key2).toContain("site2:");
      expect(key1).not.toBe(key2);
    });

    test("should handle undefined parameters", () => {
      const key1 = cacheManager.generateKey("site1", "posts");
      const key2 = cacheManager.generateKey("site1", "posts", undefined);
      const key3 = cacheManager.generateKey("site1", "posts", {});

      expect(key1).toBe(key2);
      expect(key1).toBe(key3);
    });
  });

  describe("Site-Specific Operations", () => {
    test("should clear cache for specific site", () => {
      cacheManager.set("site1:posts:abc123", "value1");
      cacheManager.set("site1:pages:def456", "value2");
      cacheManager.set("site2:posts:ghi789", "value3");

      const cleared = cacheManager.clearSite("site1");

      expect(cleared).toBe(2);
      expect(cacheManager.has("site1:posts:abc123")).toBe(false);
      expect(cacheManager.has("site1:pages:def456")).toBe(false);
      expect(cacheManager.has("site2:posts:ghi789")).toBe(true);
    });

    test("should clear cache matching pattern", () => {
      cacheManager.set("site1:posts:list", "value1");
      cacheManager.set("site1:posts:123", "value2");
      cacheManager.set("site1:pages:456", "value3");

      const pattern = /site1:posts/;
      const cleared = cacheManager.clearPattern(pattern);

      expect(cleared).toBe(2);
      expect(cacheManager.has("site1:posts:list")).toBe(false);
      expect(cacheManager.has("site1:posts:123")).toBe(false);
      expect(cacheManager.has("site1:pages:456")).toBe(true);
    });
  });

  describe("LRU Eviction", () => {
    test("should evict least recently used items when at capacity", () => {
      const smallCache = new CacheManager({
        maxSize: 3,
        defaultTTL: 10000,
        enableLRU: true,
        enableStats: true,
      });

      // Fill cache to capacity
      smallCache.set("key1", "value1");
      smallCache.set("key2", "value2");
      smallCache.set("key3", "value3");

      expect(smallCache.getStats().totalSize).toBe(3);

      // Access key1 to make it more recently used
      smallCache.get("key1");

      // Add new item, should evict key2 (least recently used)
      smallCache.set("key4", "value4");

      expect(smallCache.has("key1")).toBe(true); // Recently accessed
      expect(smallCache.has("key2")).toBe(false); // Should be evicted
      expect(smallCache.has("key3")).toBe(true); // Recently added
      expect(smallCache.has("key4")).toBe(true); // Just added
      expect(smallCache.getStats().evictions).toBe(1);
    });
  });

  describe("ETags and Conditional Requests", () => {
    test("should store and retrieve ETags", () => {
      const key = "etag-test";
      const value = { data: "test" };
      const etag = '"abc123"';

      cacheManager.set(key, value, 1000, etag);

      const entry = cacheManager.getEntry(key);
      expect(entry?.etag).toBe(etag);
    });

    test("should support conditional request headers", () => {
      const key = "conditional-test";
      const etag = '"abc123"';
      const lastModified = "Wed, 21 Oct 2015 07:28:00 GMT";

      cacheManager.set(key, { data: "test" }, 1000, etag, lastModified);

      expect(cacheManager.supportsConditionalRequest(key)).toBe(true);

      const headers = cacheManager.getConditionalHeaders(key);
      expect(headers["If-None-Match"]).toBe(etag);
      expect(headers["If-Modified-Since"]).toBe(lastModified);
    });

    test("should not support conditional requests without ETags", () => {
      const key = "no-etag-test";
      cacheManager.set(key, { data: "test" });

      expect(cacheManager.supportsConditionalRequest(key)).toBe(false);
      expect(cacheManager.getConditionalHeaders(key)).toEqual({});
    });
  });

  describe("Statistics", () => {
    test("should track cache hits and misses", () => {
      const key = "stats-test";

      // Miss
      cacheManager.get(key);
      let stats = cacheManager.getStats();
      expect(stats.misses).toBe(1);
      expect(stats.hits).toBe(0);

      // Hit
      cacheManager.set(key, "value");
      cacheManager.get(key);
      stats = cacheManager.getStats();
      expect(stats.hits).toBe(1);
      expect(stats.misses).toBe(1);
      expect(stats.hitRate).toBe(0.5);
    });

    test("should track total size", () => {
      const stats1 = cacheManager.getStats();
      expect(stats1.totalSize).toBe(0);

      cacheManager.set("key1", "value1");
      cacheManager.set("key2", "value2");

      const stats2 = cacheManager.getStats();
      expect(stats2.totalSize).toBe(2);
    });

    test("should calculate hit rate correctly", () => {
      cacheManager.set("key1", "value1");

      // 3 hits, 2 misses = 60% hit rate
      cacheManager.get("key1"); // hit
      cacheManager.get("key1"); // hit
      cacheManager.get("key1"); // hit
      cacheManager.get("key2"); // miss
      cacheManager.get("key3"); // miss

      const stats = cacheManager.getStats();
      expect(stats.hits).toBe(3);
      expect(stats.misses).toBe(2);
      expect(stats.hitRate).toBe(0.6);
    });
  });

  describe("Cache Presets", () => {
    test("should have predefined cache presets", () => {
      expect(CachePresets.STATIC.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);
      expect(CachePresets.SEMI_STATIC.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);
      expect(CachePresets.SESSION.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);

      expect(CachePresets.STATIC.cacheControl).toContain("public");
      expect(CachePresets.SESSION.cacheControl).toContain("private");
    });
  });
});
