/**
 * Performance Benchmark Tests
 *
 * These tests measure and verify performance improvements from Phase 9:
 * - Cache hit/miss performance
 * - Monitoring overhead measurement
 * - Performance utilities efficiency
 *
 * @jest-environment node
 */

import {
  measure,
  measureAsync,
  getOperationStats,
  clearMetrics,
  startTimer,
} from "../monitoring";
import { Cache, clearAllCaches, getAllCacheStats } from "../cache";

describe("Performance Benchmarks", () => {
  beforeEach(() => {
    clearMetrics();
    clearAllCaches();
  });

  describe("Cache Performance", () => {
    it("should dramatically improve repeated access performance", async () => {
      const cache = new Cache<any>(60000); // 60 second TTL

      // Expensive operation simulation
      const expensiveOperation = async () => {
        await new Promise((resolve) => setTimeout(resolve, 10)); // 10ms delay
        return { data: "expensive result" };
      };

      // First access (cache miss) - should take ~10ms
      const timer1 = startTimer("cache-miss");
      await cache.getOrSet("key", expensiveOperation);
      const missTime = timer1.end();

      // Second access (cache hit) - should be <1ms
      const timer2 = startTimer("cache-hit");
      await cache.getOrSet("key", expensiveOperation);
      const hitTime = timer2.end();

      console.log(`Cache miss: ${missTime.toFixed(2)}ms`);
      console.log(`Cache hit: ${hitTime.toFixed(2)}ms`);
      console.log(`Speedup: ${(missTime / hitTime).toFixed(2)}x`);

      // Cache hit should be at least 5x faster
      expect(missTime / hitTime).toBeGreaterThanOrEqual(5);
    });

    it("should handle high-frequency access efficiently", () => {
      const cache = new Cache<number>(60000);
      const iterations = 1000;

      // Populate cache
      for (let i = 0; i < 100; i++) {
        cache.set(`key_${i}`, i);
      }

      // Measure 1000 cache hits
      const startTime = performance.now();
      for (let i = 0; i < iterations; i++) {
        cache.get(`key_${i % 100}`);
      }
      const endTime = performance.now();
      const totalTime = endTime - startTime;
      const avgTime = totalTime / iterations;

      console.log(`1000 cache hits: ${totalTime.toFixed(2)}ms`);
      console.log(`Average per hit: ${avgTime.toFixed(4)}ms`);

      // Average cache hit should be < 0.1ms
      expect(avgTime).toBeLessThan(0.1);
    });

    it("should provide accurate hit/miss statistics", () => {
      const cache = new Cache<string>(60000);

      // Set some values
      cache.set("key1", "value1");
      cache.set("key2", "value2");
      cache.set("key3", "value3");

      // Perform hits and misses
      cache.get("key1"); // hit
      cache.get("key2"); // hit
      cache.get("key4"); // miss
      cache.get("key1"); // hit
      cache.get("key5"); // miss

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

    it("should handle cache eviction efficiently", () => {
      const cache = new Cache<string>(100); // 100ms TTL
      const iterations = 100;

      cache.set("key1", "value1");

      // Wait for expiration
      const start = performance.now();
      const waitTime = 150; // Wait longer than TTL

      return new Promise<void>((resolve) => {
        setTimeout(() => {
          const elapsed = performance.now() - start;

          // Value should be expired
          const value = cache.get("key1");
          expect(value).toBeNull();

          // Cleanup should remove it
          const removed = cache.cleanup();
          expect(removed).toBe(1);

          console.log(`Eviction test took ${elapsed.toFixed(2)}ms`);
          resolve();
        }, waitTime);
      });
    }, 10000);

    it("should scale well with many entries", () => {
      const cache = new Cache<number>(60000);
      const entryCount = 10000;

      // Insert many entries
      const insertStart = performance.now();
      for (let i = 0; i < entryCount; i++) {
        cache.set(`key_${i}`, i);
      }
      const insertTime = performance.now() - insertStart;

      // Retrieve many entries
      const retrieveStart = performance.now();
      for (let i = 0; i < entryCount; i++) {
        cache.get(`key_${i}`);
      }
      const retrieveTime = performance.now() - retrieveStart;

      console.log(
        `Inserted ${entryCount} entries in ${insertTime.toFixed(2)}ms`,
      );
      console.log(
        `Retrieved ${entryCount} entries in ${retrieveTime.toFixed(2)}ms`,
      );
      console.log(`Average insert: ${(insertTime / entryCount).toFixed(4)}ms`);
      console.log(
        `Average retrieve: ${(retrieveTime / entryCount).toFixed(4)}ms`,
      );

      // Should handle 10k entries efficiently
      expect(insertTime / entryCount).toBeLessThan(0.1);
      expect(retrieveTime / entryCount).toBeLessThan(0.05);
    });
  });

  describe("Monitoring Overhead", () => {
    it("should add minimal overhead to fast operations", async () => {
      const iterations = 1000;

      // Baseline: operation without monitoring
      const startBaseline = performance.now();
      for (let i = 0; i < iterations; i++) {
        const x = Math.sqrt(i);
      }
      const baselineTime = performance.now() - startBaseline;

      // With monitoring
      const startMonitored = performance.now();
      for (let i = 0; i < iterations; i++) {
        measure("sqrt", () => Math.sqrt(i));
      }
      const monitoredTime = performance.now() - startMonitored;

      const overhead = monitoredTime - baselineTime;
      const overheadPercent = (overhead / baselineTime) * 100;

      console.log(`Baseline: ${baselineTime.toFixed(2)}ms`);
      console.log(`Monitored: ${monitoredTime.toFixed(2)}ms`);
      console.log(
        `Overhead: ${overhead.toFixed(2)}ms (${overheadPercent.toFixed(1)}%)`,
      );

      // Monitoring overhead should be < 100% for fast operations
      expect(overheadPercent).toBeLessThan(100);
    });

    it("should efficiently track many operations", async () => {
      const operationCount = 1000;

      const start = performance.now();
      for (let i = 0; i < operationCount; i++) {
        await measureAsync(`operation-${i % 10}`, async () => {
          // Simulate lightweight async work
          return Promise.resolve(i);
        });
      }
      const elapsed = performance.now() - start;

      console.log(
        `Tracked ${operationCount} operations in ${elapsed.toFixed(2)}ms`,
      );
      console.log(
        `Average per operation: ${(elapsed / operationCount).toFixed(4)}ms`,
      );

      // Should track efficiently
      expect(elapsed / operationCount).toBeLessThan(1); // < 1ms per operation
    });

    it("should provide accurate statistics for multiple operations", () => {
      // Perform operations with known durations
      for (let i = 0; i < 10; i++) {
        measure("fast-op", () => Math.random());
      }

      for (let i = 0; i < 5; i++) {
        measure("medium-op", () => {
          const arr = Array.from({ length: 1000 }, () => Math.random());
          return arr.reduce((a, b) => a + b, 0);
        });
      }

      const fastStats = getOperationStats("fast-op");
      const mediumStats = getOperationStats("medium-op");

      expect(fastStats?.count).toBe(10);
      expect(mediumStats?.count).toBe(5);

      // Medium operations should be slower than fast operations
      if (fastStats && mediumStats) {
        console.log(`Fast op avg: ${fastStats.avgDuration.toFixed(4)}ms`);
        console.log(`Medium op avg: ${mediumStats.avgDuration.toFixed(4)}ms`);
      }
    });
  });

  describe("Combined Optimization Benefits", () => {
    it("should demonstrate cache + monitoring working together", async () => {
      const cache = new Cache<number>(60000);

      // Expensive async function
      const expensiveCalc = async (n: number): Promise<number> => {
        await new Promise((resolve) => setTimeout(resolve, 10));
        return n * n;
      };

      // Without cache (slow)
      const timer1 = startTimer("uncached");
      const result1 = await expensiveCalc(100);
      const uncachedTime = timer1.end();

      // With cache (first call - miss)
      const timer2 = startTimer("cached-miss");
      const result2 = await cache.getOrSet("calc-100", () =>
        expensiveCalc(100),
      );
      const cachedMissTime = timer2.end();

      // With cache (second call - hit)
      const timer3 = startTimer("cached-hit");
      const result3 = await cache.getOrSet("calc-100", () =>
        expensiveCalc(100),
      );
      const cachedHitTime = timer3.end();

      console.log(`Uncached: ${uncachedTime.toFixed(2)}ms`);
      console.log(`Cached (miss): ${cachedMissTime.toFixed(2)}ms`);
      console.log(`Cached (hit): ${cachedHitTime.toFixed(2)}ms`);
      console.log(
        `Speedup from caching: ${(uncachedTime / cachedHitTime).toFixed(2)}x`,
      );

      // Results should be the same
      expect(result1).toBe(result2);
      expect(result2).toBe(result3);

      // Cache hit should be dramatically faster
      expect(uncachedTime / cachedHitTime).toBeGreaterThanOrEqual(5);

      // Verify monitoring tracked all operations
      const uncachedStats = getOperationStats("uncached");
      const cachedHitStats = getOperationStats("cached-hit");

      expect(uncachedStats).toBeDefined();
      expect(cachedHitStats).toBeDefined();
    });

    it("should maintain performance under concurrent access", async () => {
      const cache = new Cache<number>(60000);
      const concurrentRequests = 100;

      const slowOperation = async (id: number): Promise<number> => {
        await new Promise((resolve) => setTimeout(resolve, 5));
        return id * 2;
      };

      // Simulate concurrent cache access
      const start = performance.now();
      const promises = Array.from({ length: concurrentRequests }, (_, i) =>
        measureAsync(`concurrent-${i}`, async () => {
          return cache.getOrSet(`key-${i % 10}`, () => slowOperation(i));
        }),
      );

      await Promise.all(promises);
      const elapsed = performance.now() - start;

      console.log(
        `${concurrentRequests} concurrent requests in ${elapsed.toFixed(2)}ms`,
      );
      console.log(
        `Average per request: ${(elapsed / concurrentRequests).toFixed(2)}ms`,
      );

      // With cache, should complete much faster than if all were uncached
      // Uncached would be: concurrentRequests * 5ms = 500ms
      // With cache (10 unique keys), should be much less
      expect(elapsed).toBeLessThan(200); // Should benefit from caching
    });
  });

  describe("Cache Statistics", () => {
    it("should track stats for multiple independent caches", () => {
      const cache1 = new Cache<string>(60000);
      const cache2 = new Cache<number>(60000);

      // Populate caches
      cache1.set("key1", "value1");
      cache1.set("key2", "value2");
      cache2.set("key1", 100);
      cache2.set("key2", 200);
      cache2.set("key3", 300);

      // Get individual stats
      const stats1 = cache1.getStats();
      const stats2 = cache2.getStats();

      expect(stats1.size).toBe(2);
      expect(stats2.size).toBe(3);

      // Perform reads to test hit rate
      cache1.get("key1"); // hit
      cache1.get("key3"); // miss
      cache2.get("key1"); // hit
      cache2.get("key2"); // hit

      const updatedStats1 = cache1.getStats();
      const updatedStats2 = cache2.getStats();

      expect(updatedStats1.hits).toBe(1);
      expect(updatedStats1.misses).toBe(1);
      expect(updatedStats2.hits).toBe(2);
      expect(updatedStats2.misses).toBe(0);
    });
  });
});
