import { CacheManager } from "@/cache/CacheManager.js";
import { performance } from "perf_hooks";
import { runEnvironmentAwarePerformanceTest, getPerformanceThresholds } from "../utils/ci-helpers.js";

describe("Cache Performance Benchmarks", () => {
  let cacheManager;

  beforeEach(() => {
    cacheManager = new CacheManager({
      maxSize: 1000,
      defaultTTL: 300000, // 5 minutes
      cleanupInterval: 10000,
    });
  });

  afterEach(async () => {
    // Cleanup to prevent worker process issues
    if (cacheManager?.destroy) {
      cacheManager.destroy();
    }
  });

  describe("Throughput Benchmarks", () => {
    it("should achieve high cache write throughput", () => {
      const iterations = 10000;
      const startTime = performance.now();

      for (let i = 0; i < iterations; i++) {
        cacheManager.set(
          `benchmark-key-${i}`,
          {
            id: i,
            data: `value-${i}`,
            timestamp: Date.now(),
            meta: { category: i % 10, priority: i % 3 },
          },
          300000,
        );
      }

      const endTime = performance.now();
      const duration = endTime - startTime;
      const throughput = iterations / (duration / 1000); // ops per second

      console.log(`Cache write throughput: ${throughput.toFixed(0)} ops/sec`);

      // Use environment-aware performance thresholds
      const thresholds = getPerformanceThresholds();
      expect(throughput).toBeGreaterThan(thresholds.CACHE_WRITE_THROUGHPUT);
      expect(cacheManager.cache.size).toBe(Math.min(iterations, 1000));
    });

    it("should achieve high cache read throughput", () => {
      // Pre-populate cache
      const testData = {};
      for (let i = 0; i < 1000; i++) {
        const data = {
          id: i,
          content: `Content for item ${i}`,
          tags: [`tag-${i % 10}`, `category-${i % 5}`],
          metadata: { views: i * 10, score: Math.random() },
        };
        cacheManager.set(`read-test-${i}`, data, 300000);
        testData[`read-test-${i}`] = data;
      }

      // Benchmark reads
      const iterations = 100000;
      const startTime = performance.now();

      for (let i = 0; i < iterations; i++) {
        const key = `read-test-${i % 1000}`;
        const result = cacheManager.get(key);
        expect(result).toEqual(testData[key]);
      }

      const endTime = performance.now();
      const duration = endTime - startTime;
      const throughput = iterations / (duration / 1000);

      console.log(`Cache read throughput: ${throughput.toFixed(0)} ops/sec`);

      // Use environment-aware performance thresholds
      const thresholds = getPerformanceThresholds();
      expect(throughput).toBeGreaterThan(thresholds.CACHE_READ_THROUGHPUT);
      expect(cacheManager.stats.hitRate).toBe(1.0); // 100% hit rate
    });

    it("should handle mixed read/write workload efficiently", () => {
      const iterations = 50000;
      const writeRatio = 0.2; // 20% writes, 80% reads

      // Pre-populate with some data
      for (let i = 0; i < 500; i++) {
        cacheManager.set(`mixed-${i}`, { id: i, value: `initial-${i}` }, 300000);
      }

      const startTime = performance.now();
      let reads = 0;
      let writes = 0;

      for (let i = 0; i < iterations; i++) {
        if (Math.random() < writeRatio) {
          // Write operation
          cacheManager.set(
            `mixed-${i % 1000}`,
            {
              id: i,
              value: `updated-${i}`,
              timestamp: Date.now(),
            },
            300000,
          );
          writes++;
        } else {
          // Read operation
          const key = `mixed-${i % 500}`;
          cacheManager.get(key);
          reads++;
        }
      }

      const endTime = performance.now();
      const duration = endTime - startTime;
      const throughput = iterations / (duration / 1000);

      console.log(`Mixed workload throughput: ${throughput.toFixed(0)} ops/sec (${reads} reads, ${writes} writes)`);

      // Use environment-aware performance thresholds
      const thresholds = getPerformanceThresholds();
      expect(throughput).toBeGreaterThan(thresholds.MIXED_WORKLOAD_THROUGHPUT);
      expect(reads + writes).toBe(iterations);
      expect(writes / iterations).toBeCloseTo(writeRatio, 1);
    });
  });

  describe("Latency Benchmarks", () => {
    it("should have low cache operation latency", () => {
      const iterations = 1000;
      const latencies = [];

      // Measure individual operation latencies
      for (let i = 0; i < iterations; i++) {
        const key = `latency-test-${i}`;
        const data = {
          id: i,
          content: `Test content ${i}`,
          array: new Array(100).fill(i),
        };

        // Measure set latency
        const setStart = performance.now();
        cacheManager.set(key, data, 300000);
        const setEnd = performance.now();
        latencies.push(setEnd - setStart);

        // Measure get latency
        const getStart = performance.now();
        const result = cacheManager.get(key);
        const getEnd = performance.now();
        latencies.push(getEnd - getStart);

        expect(result).toEqual(data);
      }

      // Calculate percentiles
      latencies.sort((a, b) => a - b);
      const p50 = latencies[Math.floor(latencies.length * 0.5)];
      const p95 = latencies[Math.floor(latencies.length * 0.95)];
      const p99 = latencies[Math.floor(latencies.length * 0.99)];
      const max = latencies[latencies.length - 1];

      console.log(
        `Cache operation latencies - P50: ${p50.toFixed(3)}ms, P95: ${p95.toFixed(3)}ms, P99: ${p99.toFixed(
          3,
        )}ms, Max: ${max.toFixed(3)}ms`,
      );

      // Latency assertions
      expect(p50).toBeLessThan(0.1); // 50% under 0.1ms
      expect(p95).toBeLessThan(1.0); // 95% under 1ms
      expect(p99).toBeLessThan(5.0); // 99% under 5ms
    });

    it("should maintain low latency under memory pressure", () => {
      // Create cache that will trigger evictions
      const pressureCache = new CacheManager({
        maxSize: 100,
        defaultTTL: 300000,
      });

      const latencies = [];
      const iterations = 1000;

      for (let i = 0; i < iterations; i++) {
        const data = {
          id: i,
          content: `Large content item ${i}`,
          largeArray: new Array(1000).fill(`item-${i}`),
          metadata: {
            created: Date.now(),
            tags: Array(20)
              .fill(null)
              .map((_, j) => `tag-${i}-${j}`),
          },
        };

        const start = performance.now();
        pressureCache.set(`pressure-${i}`, data, 300000);
        const end = performance.now();

        latencies.push(end - start);
      }

      // Even under pressure, latencies should remain reasonable
      latencies.sort((a, b) => a - b);
      const p95 = latencies[Math.floor(latencies.length * 0.95)];

      console.log(`Latency under pressure - P95: ${p95.toFixed(3)}ms`);

      expect(p95).toBeLessThan(10.0); // Should stay under 10ms even with evictions
      expect(pressureCache.cache.size).toBeLessThanOrEqual(100);
      expect(pressureCache.stats.evictions).toBeGreaterThan(0);

      // Clean up the cache instance
      pressureCache.destroy();
    });
  });

  describe("Memory Efficiency Benchmarks", () => {
    it("should efficiently use memory for cached objects", () => {
      const objectSizes = [
        { name: "small", data: { id: 1, title: "Test" } },
        {
          name: "medium",
          data: { id: 1, title: "Test", content: "x".repeat(1000) },
        },
        {
          name: "large",
          data: {
            id: 1,
            title: "Test",
            content: "x".repeat(10000),
            meta: new Array(100).fill("data"),
          },
        },
      ];

      objectSizes.forEach(({ name, data }) => {
        const cache = new CacheManager({ maxSize: 1000 });
        const itemCount = 100;

        // Measure memory before
        const beforeMemory = process.memoryUsage().heapUsed;

        // Add items to cache
        for (let i = 0; i < itemCount; i++) {
          cache.set(`${name}-${i}`, { ...data, id: i }, 300000);
        }

        // Measure memory after
        const afterMemory = process.memoryUsage().heapUsed;
        const memoryIncrease = afterMemory - beforeMemory;
        const memoryPerItem = memoryIncrease / itemCount;

        console.log(`Memory usage for ${name} objects: ${memoryPerItem} bytes per item`);

        // Verify all items are cached
        expect(cache.cache.size).toBe(itemCount);

        // Memory per item should scale reasonably with object size
        // More lenient thresholds for CI environments
        const expectedThreshold = name === "small" ? 5000 : name === "medium" ? 10000 : 100000;
        expect(memoryPerItem).toBeLessThan(expectedThreshold);

        // Clean up the cache instance
        cache.destroy();
      });
    });

    it("should maintain stable memory usage during continuous operation", async () => {
      const cache = new CacheManager({
        maxSize: 500,
        defaultTTL: 1000, // Short TTL to test cleanup
        cleanupInterval: 100,
      });

      const memoryMeasurements = [];
      const measureMemory = () => {
        const usage = process.memoryUsage();
        memoryMeasurements.push({
          timestamp: Date.now(),
          heapUsed: usage.heapUsed,
          cacheSize: cache.cache.size,
        });
      };

      // Initial measurement
      measureMemory();

      // Simulate continuous operation
      const operations = 2000;
      for (let i = 0; i < operations; i++) {
        if (i % 200 === 0) measureMemory();

        // Mix of operations
        if (i % 10 === 0) {
          // Expired item access
          cache.get(`expired-${i - 100}`);
        } else if (i % 7 === 0) {
          // Large object
          cache.set(
            `large-${i}`,
            {
              id: i,
              data: new Array(500).fill(`item-${i}`),
              timestamp: Date.now(),
            },
            1000,
          );
        } else {
          // Normal object
          cache.set(
            `normal-${i}`,
            {
              id: i,
              value: `value-${i}`,
              meta: { type: "normal" },
            },
            5000,
          );
        }

        if (i % 50 === 0) {
          await new Promise((resolve) => setTimeout(resolve, 10));
        }
      }

      // Final measurement
      await new Promise((resolve) => setTimeout(resolve, 200));
      measureMemory();

      // Analyze memory stability
      const heapSizes = memoryMeasurements.map((m) => m.heapUsed);
      const maxHeap = Math.max(...heapSizes);
      const minHeap = Math.min(...heapSizes);
      const heapVariation = (maxHeap - minHeap) / minHeap;

      console.log(
        `Memory stability - Min: ${(minHeap / 1024 / 1024).toFixed(1)}MB, Max: ${(maxHeap / 1024 / 1024).toFixed(
          1,
        )}MB, Variation: ${(heapVariation * 100).toFixed(1)}%`,
      );

      // Memory should not continuously grow excessively
      // Account for Node.js GC behavior - allow for more variation in test environments
      expect(heapVariation).toBeLessThan(20.0); // Less than 2000% variation (adjusted for GC)
      expect(cache.cache.size).toBeLessThanOrEqual(500);

      // Clean up the cache instance
      cache.destroy();
    });
  });

  describe("Scalability Benchmarks", () => {
    it("should scale performance with cache size", () => {
      const cacheSizes = [100, 500, 1000, 5000];
      const results = [];

      cacheSizes.forEach((size) => {
        const cache = new CacheManager({
          maxSize: size,
          defaultTTL: 300000,
        });

        // Fill cache to capacity
        const fillStart = performance.now();
        for (let i = 0; i < size; i++) {
          cache.set(
            `scale-${i}`,
            {
              id: i,
              data: `scaled-data-${i}`,
              meta: { size, index: i },
            },
            300000,
          );
        }
        const fillEnd = performance.now();

        // Measure access time at capacity
        const accessIterations = 1000;
        const accessStart = performance.now();
        for (let i = 0; i < accessIterations; i++) {
          cache.get(`scale-${i % size}`);
        }
        const accessEnd = performance.now();

        const fillThroughput = size / ((fillEnd - fillStart) / 1000);
        const accessThroughput = accessIterations / ((accessEnd - accessStart) / 1000);

        results.push({
          size,
          fillThroughput,
          accessThroughput,
          hitRate: cache.stats.hitRate,
        });

        console.log(
          `Cache size ${size}: Fill=${fillThroughput.toFixed(0)} ops/sec, Access=${accessThroughput.toFixed(
            0,
          )} ops/sec`,
        );

        // Clean up the cache instance
        cache.destroy();
      });

      // Verify scalability characteristics
      results.forEach((result) => {
        expect(result.hitRate).toBe(1.0); // Should maintain 100% hit rate
      });

      // Check performance scaling between consecutive results
      runEnvironmentAwarePerformanceTest(
        // Local environment: Full scaling assertions
        () => {
          for (let index = 1; index < results.length; index++) {
            const result = results[index];
            const previous = results[index - 1];
            // Performance shouldn't degrade dramatically with size
            // More lenient threshold for VS Code/CI environments
            expect(result.accessThroughput).toBeGreaterThan(previous.accessThroughput * 0.3);
          }
        },
        // CI environment: Basic throughput validation
        () => {
          const thresholds = getPerformanceThresholds();
          results.forEach((result) => {
            expect(result.accessThroughput).toBeGreaterThan(thresholds.CACHE_READ_THROUGHPUT);
          });
        },
      );
    });

    it("should handle high concurrency efficiently", async () => {
      const cache = new CacheManager({
        maxSize: 1000,
        defaultTTL: 300000,
      });

      const concurrencyLevels = [1, 10, 50, 100];
      const results = [];

      for (const concurrency of concurrencyLevels) {
        const operationsPerWorker = 1000;
        const totalOperations = concurrency * operationsPerWorker;

        const startTime = performance.now();

        // Create concurrent workers
        const workers = Array(concurrency)
          .fill(null)
          .map(async (_, workerIndex) => {
            for (let i = 0; i < operationsPerWorker; i++) {
              const key = `concurrent-${workerIndex}-${i}`;

              if (i % 3 === 0) {
                // Write
                cache.set(
                  key,
                  {
                    worker: workerIndex,
                    operation: i,
                    timestamp: Date.now(),
                  },
                  300000,
                );
              } else {
                // Read (might miss for new keys)
                cache.get(key);
              }
            }
          });

        await Promise.all(workers);

        const endTime = performance.now();
        const duration = endTime - startTime;
        const throughput = totalOperations / (duration / 1000);

        results.push({
          concurrency,
          throughput,
          cacheSize: cache.cache.size,
        });

        console.log(`Concurrency ${concurrency}: ${throughput.toFixed(0)} ops/sec, Cache size: ${cache.cache.size}`);
      }

      // Verify concurrency handling
      results.forEach((result) => {
        expect(result.cacheSize).toBeLessThanOrEqual(1000);
      });

      // Verify all concurrency levels maintain acceptable throughput
      // (Monotonic scaling is not expected in single-threaded JS for in-memory ops)
      const thresholds = getPerformanceThresholds();
      results.forEach((result) => {
        expect(result.throughput).toBeGreaterThan(thresholds.MIXED_WORKLOAD_THROUGHPUT);
      });

      // Clean up the cache instance
      cache.destroy();
    });
  });
});
