/**
 * Tests for Web Vitals Monitoring
 *
 * @jest-environment jsdom
 */

import type { Metric } from "web-vitals";
import {
  reportWebVitals,
  getCurrentMetrics,
  METRIC_THRESHOLDS,
} from "@/lib/monitoring/web-vitals";

// Helper to set NODE_ENV without TypeScript errors
const setNodeEnv = (value: string) => {
  (process.env as Record<string, string>).NODE_ENV = value;
};

// Mock web-vitals module
const mockOnCLS = jest.fn();
const mockOnFID = jest.fn();
const mockOnFCP = jest.fn();
const mockOnLCP = jest.fn();
const mockOnTTFB = jest.fn();
const mockOnINP = jest.fn();

jest.mock("web-vitals", () => ({
  onCLS: mockOnCLS,
  onFID: mockOnFID,
  onFCP: mockOnFCP,
  onLCP: mockOnLCP,
  onTTFB: mockOnTTFB,
  onINP: mockOnINP,
}));

// Mock Sentry
const mockSetMeasurement = jest.fn();
const mockAddBreadcrumb = jest.fn();
const mockCaptureMessage = jest.fn();

jest.mock(
  "@sentry/nextjs",
  () => ({
    setMeasurement: mockSetMeasurement,
    addBreadcrumb: mockAddBreadcrumb,
    captureMessage: mockCaptureMessage,
  }),
  { virtual: true },
);

// Mock gtag
const mockGtag = jest.fn();
(global as unknown as { gtag: typeof mockGtag }).gtag = mockGtag;

/**
 * Helper to create a mock metric
 */
function createMockMetric(
  name: "CLS" | "FID" | "FCP" | "LCP" | "TTFB" | "INP",
  value: number,
): Metric {
  return {
    name,
    value,
    rating: "good",
    delta: value,
    id: `test-${name}-${Date.now()}`,
    navigationType: "navigate",
    entries: [],
  } as Metric;
}

describe("Web Vitals Monitoring", () => {
  beforeEach(() => {
    jest.clearAllMocks();
    // Reset console methods
    jest.spyOn(console, "debug").mockImplementation();
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  describe("reportWebVitals", () => {
    it.skip("should initialize all web vitals listeners", () => {
      reportWebVitals();

      // Wait for dynamic import
      return new Promise<void>((resolve) => {
        setTimeout(() => {
          expect(mockOnCLS).toHaveBeenCalledWith(expect.any(Function));
          expect(mockOnFID).toHaveBeenCalledWith(expect.any(Function));
          expect(mockOnFCP).toHaveBeenCalledWith(expect.any(Function));
          expect(mockOnLCP).toHaveBeenCalledWith(expect.any(Function));
          expect(mockOnTTFB).toHaveBeenCalledWith(expect.any(Function));
          expect(mockOnINP).toHaveBeenCalledWith(expect.any(Function));
          resolve();
        }, 100);
      });
    });

    it("should not run on server side", () => {
      // Mock SSR environment
      const originalWindow = global.window;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      delete (global as any).window;

      reportWebVitals();

      expect(mockOnCLS).not.toHaveBeenCalled();

      // Restore
      global.window = originalWindow;
    });
  });

  describe("Metric Tracking", () => {
    it("should send LCP metric to Sentry", async () => {
      const metric = createMockMetric("LCP", 2000);

      // Trigger the callback
      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockSetMeasurement).toHaveBeenCalledWith(
              "LCP",
              2000,
              "millisecond",
            );
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                category: "web-vitals",
                message: expect.stringContaining("LCP"),
                level: "info",
                data: expect.objectContaining({
                  value: 2000,
                  rating: "good",
                }),
              }),
            );
            resolve();
          }, 100);
        }, 100);
      });
    });

    it.skip("should send FID metric to Sentry", async () => {
      const metric = createMockMetric("FID", 50);

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnFID.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockSetMeasurement).toHaveBeenCalledWith(
              "FID",
              50,
              "millisecond",
            );
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                category: "web-vitals",
                message: expect.stringContaining("FID"),
                data: expect.objectContaining({
                  value: 50,
                  rating: "good",
                }),
              }),
            );
            resolve();
          }, 100);
        }, 100);
      });
    });

    it("should send CLS metric to Sentry", async () => {
      const metric = createMockMetric("CLS", 0.05);

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnCLS.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockSetMeasurement).toHaveBeenCalledWith(
              "CLS",
              0.05,
              "millisecond",
            );
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                category: "web-vitals",
                message: expect.stringContaining("CLS"),
                data: expect.objectContaining({
                  value: 0.05,
                  rating: "good",
                }),
              }),
            );
            resolve();
          }, 100);
        }, 100);
      });
    });
  });

  describe("Performance Rating", () => {
    it("should rate good LCP performance", async () => {
      const metric = createMockMetric("LCP", 2000); // Good: < 2500ms

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                level: "info",
                data: expect.objectContaining({
                  rating: "good",
                }),
              }),
            );
            expect(mockCaptureMessage).not.toHaveBeenCalled();
            resolve();
          }, 100);
        }, 100);
      });
    });

    it("should rate needs-improvement LCP performance", async () => {
      const metric = createMockMetric("LCP", 3000); // Needs improvement: 2500-4000ms

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                level: "info",
                data: expect.objectContaining({
                  rating: "needs-improvement",
                }),
              }),
            );
            resolve();
          }, 100);
        }, 100);
      });
    });

    it("should rate poor LCP performance and capture warning", async () => {
      const metric = createMockMetric("LCP", 5000); // Poor: > 4000ms

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockAddBreadcrumb).toHaveBeenCalledWith(
              expect.objectContaining({
                level: "warning",
                data: expect.objectContaining({
                  rating: "poor",
                }),
              }),
            );
            expect(mockCaptureMessage).toHaveBeenCalledWith(
              "Poor LCP performance detected",
              expect.objectContaining({
                level: "warning",
                tags: expect.objectContaining({
                  metric: "LCP",
                  rating: "poor",
                }),
              }),
            );
            resolve();
          }, 100);
        }, 100);
      });
    });
  });

  describe("Analytics Integration", () => {
    it("should send metrics to Google Analytics if available", async () => {
      const metric = createMockMetric("LCP", 2000);

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            expect(mockGtag).toHaveBeenCalledWith("event", "LCP", {
              value: 2000,
              event_category: "Web Vitals",
              event_label: expect.stringContaining("test-LCP"),
              non_interaction: true,
            });
            resolve();
          }, 100);
        }, 100);
      });
    });

    it("should handle CLS metric differently for GA4", async () => {
      const metric = createMockMetric("CLS", 0.05);

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnCLS.mock.calls[0][0];
          callback(metric);

          setTimeout(() => {
            // CLS is multiplied by 1000 for GA4
            expect(mockGtag).toHaveBeenCalledWith("event", "CLS", {
              value: 50, // 0.05 * 1000
              event_category: "Web Vitals",
              event_label: expect.stringContaining("test-CLS"),
              non_interaction: true,
            });
            resolve();
          }, 100);
        }, 100);
      });
    });
  });

  describe("getCurrentMetrics", () => {
    it.skip("should collect all current metrics", async () => {
      const metrics = await getCurrentMetrics();

      // Should attempt to get all metrics
      expect(mockOnCLS).toHaveBeenCalled();
      expect(mockOnFID).toHaveBeenCalled();
      expect(mockOnFCP).toHaveBeenCalled();
      expect(mockOnLCP).toHaveBeenCalled();
      expect(mockOnTTFB).toHaveBeenCalled();
      expect(mockOnINP).toHaveBeenCalled();

      // Should return an array (empty in test env)
      expect(Array.isArray(metrics)).toBe(true);
    });

    it("should return empty array on server side", async () => {
      const originalWindow = global.window;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      delete (global as any).window;

      const metrics = await getCurrentMetrics();

      expect(metrics).toEqual([]);

      // Restore
      global.window = originalWindow;
    });
  });

  describe("METRIC_THRESHOLDS", () => {
    it("should export valid thresholds", () => {
      expect(METRIC_THRESHOLDS).toBeDefined();
      expect(METRIC_THRESHOLDS.LCP).toEqual({
        good: 2500,
        needsImprovement: 4000,
      });
      expect(METRIC_THRESHOLDS.CLS).toEqual({
        good: 0.1,
        needsImprovement: 0.25,
      });
      expect(METRIC_THRESHOLDS.TTFB).toEqual({
        good: 800,
        needsImprovement: 1800,
      });
      expect(METRIC_THRESHOLDS.FCP).toEqual({
        good: 1800,
        needsImprovement: 3000,
      });
      expect(METRIC_THRESHOLDS.INP).toEqual({
        good: 200,
        needsImprovement: 500,
      });
    });
  });

  describe("Error Handling", () => {
    it("should gracefully handle missing Sentry", async () => {
      // Clear the mock to simulate Sentry not being available
      jest.resetModules();

      const metric = createMockMetric("LCP", 2000);

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          const callback = mockOnLCP.mock.calls[0][0];

          // Should not throw
          expect(() => callback(metric)).not.toThrow();
          resolve();
        }, 100);
      });
    });

    it("should log in development when web-vitals is unavailable", async () => {
      const consoleSpy = jest.spyOn(console, "debug");
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("development");

      // Temporarily break the import
      jest.resetModules();
      jest.doMock("web-vitals", () => {
        throw new Error("Not available");
      });

      reportWebVitals();

      await new Promise<void>((resolve) => {
        setTimeout(() => {
          // Should have logged
          expect(consoleSpy).toHaveBeenCalledWith(
            expect.stringContaining("web-vitals"),
          );
          resolve();
          setNodeEnv(originalEnv || "test");
        }, 100);
      });
    });
  });
});
