/**
 * @jest-environment node
 */
import {
  startTimer,
  measure,
  measureAsync,
  getOperationStats,
  getAllStats,
  getRecentMetrics,
  clearMetrics,
} from "../monitoring";

describe("Performance Monitoring", () => {
  beforeEach(() => {
    // Clear metrics before each test
    clearMetrics();
  });

  describe("startTimer", () => {
    it("should create a timer and measure duration", async () => {
      const timer = startTimer("test-operation");

      // Simulate some work
      await new Promise((resolve) => setTimeout(resolve, 10));

      const duration = timer.end();

      expect(duration).toBeGreaterThanOrEqual(10);
      expect(duration).toBeLessThan(50); // Allow some overhead
    });

    it("should record metric with metadata", () => {
      const timer = startTimer("test-operation");
      timer.end({ count: 100, success: true });

      const stats = getOperationStats("test-operation");
      expect(stats).not.toBeNull();
      expect(stats?.count).toBe(1);

      const metrics = getRecentMetrics(1);
      expect(metrics).toHaveLength(1);
      expect(metrics[0].metadata).toEqual({ count: 100, success: true });
    });
  });

  describe("measure", () => {
    it("should measure synchronous function execution", () => {
      let result = 0;
      const returnValue = measure("sync-operation", () => {
        for (let i = 0; i < 100; i++) {
          result += i;
        }
        return result;
      });

      expect(returnValue).toBe(result);

      const stats = getOperationStats("sync-operation");
      expect(stats).not.toBeNull();
      expect(stats?.count).toBe(1);
      expect(stats?.avgDuration).toBeGreaterThan(0);
    });
  });

  describe("measureAsync", () => {
    it("should measure async function execution", async () => {
      const result = await measureAsync("async-operation", async () => {
        await new Promise((resolve) => setTimeout(resolve, 10));
        return "test-result";
      });

      expect(result).toBe("test-result");

      const stats = getOperationStats("async-operation");
      expect(stats).not.toBeNull();
      expect(stats?.count).toBe(1);
      expect(stats?.avgDuration).toBeGreaterThanOrEqual(10);
    });

    it("should record error in metadata when function throws", async () => {
      await expect(
        measureAsync("failing-operation", async () => {
          throw new Error("Test error");
        }),
      ).rejects.toThrow("Test error");

      const metrics = getRecentMetrics(1);
      expect(metrics).toHaveLength(1);
      expect(metrics[0].metadata).toEqual({ error: true });
    });

    it("should support custom metadata", async () => {
      await measureAsync(
        "async-operation-with-metadata",
        async () => {
          return "result";
        },
        { userId: "123", action: "fetch" },
      );

      const metrics = getRecentMetrics(1);
      expect(metrics[0].metadata).toEqual({
        userId: "123",
        action: "fetch",
      });
    });
  });

  describe("getOperationStats", () => {
    it("should return null for unknown operation", () => {
      const stats = getOperationStats("nonexistent-operation");
      expect(stats).toBeNull();
    });

    it("should calculate correct statistics for multiple operations", async () => {
      // Create operations with different durations
      await measureAsync("multi-operation", async () => {
        await new Promise((resolve) => setTimeout(resolve, 10));
      });
      await measureAsync("multi-operation", async () => {
        await new Promise((resolve) => setTimeout(resolve, 20));
      });
      await measureAsync("multi-operation", async () => {
        await new Promise((resolve) => setTimeout(resolve, 15));
      });

      const stats = getOperationStats("multi-operation");
      expect(stats).not.toBeNull();
      expect(stats?.count).toBe(3);
      expect(stats?.minDuration).toBeGreaterThanOrEqual(10);
      expect(stats?.maxDuration).toBeGreaterThanOrEqual(20);
      expect(stats?.avgDuration).toBeGreaterThanOrEqual(10);
      expect(stats?.avgDuration).toBeLessThanOrEqual(30);
      expect(stats?.totalDuration).toBe(stats!.avgDuration * 3);
    });
  });

  describe("getAllStats", () => {
    it("should return empty object when no operations recorded", () => {
      const stats = getAllStats();
      expect(stats).toEqual({});
    });

    it("should return stats for all operations", async () => {
      await measureAsync("operation-1", async () => {});
      await measureAsync("operation-2", async () => {});
      await measureAsync("operation-1", async () => {});

      const stats = getAllStats();
      expect(Object.keys(stats)).toHaveLength(2);
      expect(stats["operation-1"]).toBeDefined();
      expect(stats["operation-2"]).toBeDefined();
      expect(stats["operation-1"].count).toBe(2);
      expect(stats["operation-2"].count).toBe(1);
    });
  });

  describe("getRecentMetrics", () => {
    it("should return empty array when no metrics", () => {
      const metrics = getRecentMetrics();
      expect(metrics).toEqual([]);
    });

    it("should return limited number of metrics", async () => {
      for (let i = 0; i < 10; i++) {
        await measureAsync(`operation-${i}`, async () => {});
      }

      const metrics = getRecentMetrics(5);
      expect(metrics).toHaveLength(5);
    });

    it("should return all metrics when no limit specified", async () => {
      for (let i = 0; i < 10; i++) {
        await measureAsync(`operation-${i}`, async () => {});
      }

      const metrics = getRecentMetrics();
      expect(metrics).toHaveLength(10);
    });
  });

  describe("clearMetrics", () => {
    it("should clear all recorded metrics", async () => {
      await measureAsync("operation-1", async () => {});
      await measureAsync("operation-2", async () => {});

      let metrics = getRecentMetrics();
      expect(metrics.length).toBeGreaterThan(0);

      clearMetrics();

      metrics = getRecentMetrics();
      expect(metrics).toEqual([]);

      const stats = getAllStats();
      expect(stats).toEqual({});
    });
  });

  describe("Metrics History", () => {
    it("should maintain last 1000 metrics", async () => {
      // Create 1100 metrics
      for (let i = 0; i < 1100; i++) {
        const timer = startTimer("history-test");
        timer.end();
      }

      const metrics = getRecentMetrics();
      // Should keep only 1000
      expect(metrics.length).toBeLessThanOrEqual(1000);
    });
  });
});
