/**
 * Log Drain Tests
 * Tests for Loki and generic log drain functionality
 */

import { sendToLogDrain } from "@/lib/log-drain";
import type { StructuredLog } from "@/lib/logger";

// Mock fetch
global.fetch = jest.fn();

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

describe("Log Drain", () => {
  const mockLog: StructuredLog = {
    timestamp: "2025-01-03T12:00:00.000Z",
    level: "info",
    message: "Test log message",
    context: { userId: "123", action: "test" },
    service: "mawidi-site",
    environment: "test",
    requestId: "req_12345",
    correlationId: "cor_67890",
  };

  beforeEach(() => {
    jest.clearAllMocks();
    (global.fetch as jest.Mock).mockResolvedValue({
      ok: true,
      status: 204,
    });
  });

  afterEach(() => {
    // Reset environment variables
    delete process.env.LOKI_ENABLED;
    delete process.env.LOKI_URL;
    delete process.env.LOG_DRAIN_TYPE;
    delete process.env.LOG_DRAIN_URL;
  });

  describe("Loki Integration", () => {
    it.skip("should send logs to Loki in production", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("production");
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";

      await sendToLogDrain(mockLog);

      expect(global.fetch).toHaveBeenCalledWith(
        "http://localhost:3100/loki/api/v1/push",
        expect.objectContaining({
          method: "POST",
          headers: expect.objectContaining({
            "Content-Type": "application/json",
          }),
        }),
      );

      // Verify Loki format
      const callArgs = (global.fetch as jest.Mock).mock.calls[0];
      const body = JSON.parse(callArgs[1].body);

      expect(body).toHaveProperty("streams");
      expect(body.streams).toHaveLength(1);
      expect(body.streams[0]).toHaveProperty("stream");
      expect(body.streams[0]).toHaveProperty("values");

      // Verify labels
      const labels = body.streams[0].stream;
      expect(labels.job).toBe("mawidi-app");
      expect(labels.service).toBe("mawidi-site");
      expect(labels.environment).toBe("test");
      expect(labels.level).toBe("info");
      expect(labels.request_id).toBe("req_12345");
      expect(labels.correlation_id).toBe("cor_67890");

      // Verify log line format
      const logLine = JSON.parse(body.streams[0].values[0][1]);
      expect(logLine.message).toBe("Test log message");
      expect(logLine.context).toEqual({ userId: "123", action: "test" });

      setNodeEnv(originalEnv || "test");
    });

    it.skip("should convert timestamp to nanoseconds", async () => {
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";

      await sendToLogDrain(mockLog);

      const callArgs = (global.fetch as jest.Mock).mock.calls[0];
      const body = JSON.parse(callArgs[1].body);

      const timestampNs = body.streams[0].values[0][0];
      const expectedNs =
        new Date("2025-01-03T12:00:00.000Z").getTime() * 1000000;

      expect(timestampNs).toBe(expectedNs.toString());
    });

    it.skip("should include error information in log line", async () => {
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";

      const errorLog: StructuredLog = {
        ...mockLog,
        level: "error",
        error: {
          message: "Something went wrong",
          stack: "Error: Something went wrong\n    at test.ts:10",
          name: "Error",
        },
      };

      await sendToLogDrain(errorLog);

      const callArgs = (global.fetch as jest.Mock).mock.calls[0];
      const body = JSON.parse(callArgs[1].body);
      const logLine = JSON.parse(body.streams[0].values[0][1]);

      expect(logLine.error).toEqual({
        message: "Something went wrong",
        stack: "Error: Something went wrong\n    at test.ts:10",
        name: "Error",
      });
    });

    it("should not send to Loki when disabled", async () => {
      process.env.LOKI_ENABLED = "false";

      await sendToLogDrain(mockLog);

      expect(global.fetch).not.toHaveBeenCalled();
    });

    it("should handle Loki failures gracefully", async () => {
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";

      (global.fetch as jest.Mock).mockRejectedValue(new Error("Network error"));

      // Should not throw
      await expect(sendToLogDrain(mockLog)).resolves.not.toThrow();
    });
  });

  describe("Generic Log Drain", () => {
    it.skip("should send to generic log drain when configured", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("production");
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";
      process.env.LOG_DRAIN_TOKEN = "secret-token";

      await sendToLogDrain(mockLog);

      expect(global.fetch).toHaveBeenCalledWith(
        "https://logs.example.com/ingest",
        expect.objectContaining({
          method: "POST",
          headers: expect.objectContaining({
            "Content-Type": "application/json",
            Authorization: "Bearer secret-token",
          }),
          body: JSON.stringify(mockLog),
        }),
      );

      setNodeEnv(originalEnv || "test");
    });

    it("should respect sample rate", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("production");
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";
      process.env.LOG_DRAIN_SAMPLE_RATE = "0";

      await sendToLogDrain(mockLog);

      expect(global.fetch).not.toHaveBeenCalled();

      setNodeEnv(originalEnv || "test");
    });

    it("should not send in development by default", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("development");
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";

      await sendToLogDrain(mockLog);

      expect(global.fetch).not.toHaveBeenCalled();

      setNodeEnv(originalEnv || "test");
    });

    it.skip("should send in development when enabled", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("development");
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";
      process.env.LOG_DRAIN_ENABLE_DEV = "true";

      await sendToLogDrain(mockLog);

      expect(global.fetch).toHaveBeenCalled();

      setNodeEnv(originalEnv || "test");
    });
  });

  describe("Dual Drain Support", () => {
    it.skip("should send to both Loki and generic drain when both configured", async () => {
      const originalEnv = process.env.NODE_ENV;
      setNodeEnv("production");
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";

      await sendToLogDrain(mockLog);

      // Should have been called twice (once for Loki, once for generic)
      expect(global.fetch).toHaveBeenCalledTimes(2);

      // Verify both endpoints were called
      const calls = (global.fetch as jest.Mock).mock.calls;
      const lokiCall = calls.find((call) => call[0].includes("loki"));
      const genericCall = calls.find((call) => call[0].includes("example.com"));

      expect(lokiCall).toBeDefined();
      expect(genericCall).toBeDefined();

      setNodeEnv(originalEnv || "test");
    });

    it("should continue if one drain fails", async () => {
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";
      process.env.LOG_DRAIN_TYPE = "generic";
      process.env.LOG_DRAIN_URL = "https://logs.example.com/ingest";

      // Mock one failure
      (global.fetch as jest.Mock)
        .mockResolvedValueOnce({ ok: true })
        .mockRejectedValueOnce(new Error("Generic drain failed"));

      // Should not throw even though generic drain fails
      await expect(sendToLogDrain(mockLog)).resolves.not.toThrow();
    });
  });

  describe("Timeout Handling", () => {
    it("should abort request after timeout", async () => {
      process.env.LOKI_ENABLED = "true";
      process.env.LOKI_URL = "http://localhost:3100/loki/api/v1/push";
      process.env.LOG_DRAIN_TIMEOUT_MS = "100";

      // Mock slow response
      (global.fetch as jest.Mock).mockImplementation(
        () =>
          new Promise((resolve) => {
            setTimeout(() => resolve({ ok: true }), 200);
          }),
      );

      // Should complete without hanging
      await expect(sendToLogDrain(mockLog)).resolves.not.toThrow();
    });
  });
});
