/*
 * Copyright 2025 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import AioLogger from "@adobe/aio-lib-core-logging";
import { DiagLogLevel, diag } from "@opentelemetry/api";
import { beforeEach, describe, expect, test, vi } from "vitest";

import { getLogger, setOtelDiagLogger } from "#core/logging";
import * as sdkModule from "#core/sdk";
import * as runtimeHelpers from "#helpers/runtime";

const mockOpenTelemetryTransport = vi.hoisted(() => vi.fn());

vi.mock("@adobe/aio-lib-core-logging", () => ({
  default: vi.fn(),
}));

vi.mock("@opentelemetry/winston-transport", () => ({
  OpenTelemetryTransportV3: mockOpenTelemetryTransport,
}));

vi.mock("@opentelemetry/api", () => ({
  DiagLogLevel: {
    ALL: 6,
    DEBUG: 4,
    ERROR: 1,
    INFO: 3,
    NONE: 0,
    VERBOSE: 5,
    WARN: 2,
  },
  diag: {
    error: vi.fn(),
    info: vi.fn(),
    setLogger: vi.fn(),
  },
}));

vi.mock("#helpers/runtime", () => ({
  getRuntimeActionMetadata: vi.fn(),
  getRuntimeInvocationAttributes: vi.fn(() => ({
    "action.activation_id": "test-activation",
    "action.region": "test-region",
  })),
}));

vi.mock("#core/sdk", () => ({
  ensureSdkInitialized: vi.fn(),
}));

describe("core/logging", () => {
  const mockLogger = {
    debug: vi.fn(),
    error: vi.fn(),
    info: vi.fn(),
    logger: {
      logger: {
        add: vi.fn(),
      },
    },
    verbose: vi.fn(),
    warn: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(AioLogger).mockReturnValue(
      mockLogger as unknown as ReturnType<typeof AioLogger>,
    );

    vi.mocked(runtimeHelpers.getRuntimeActionMetadata).mockReturnValue({
      actionName: "test-action",
    } as ReturnType<typeof runtimeHelpers.getRuntimeActionMetadata>);

    mockOpenTelemetryTransport.mockImplementation(function (this: unknown) {
      return {};
    });
  });

  describe("getLogger", () => {
    test("should create a logger with default configuration", () => {
      const logger = getLogger("test-logger");
      expect(logger).toBe(mockLogger);

      expect(sdkModule.ensureSdkInitialized).toHaveBeenCalled();
      expect(AioLogger).toHaveBeenCalledWith("test-logger", {
        level: "info",
        provider: "winston",
      });
    });

    test("should create a logger with custom configuration", () => {
      const customConfig = {
        level: "debug",
        logSourceAction: false,
      };

      const logger = getLogger("custom-logger", customConfig);
      expect(logger).toBe(mockLogger);

      expect(AioLogger).toHaveBeenCalledWith("custom-logger", {
        ...customConfig,
        provider: "winston",
      });
    });

    test("should add OpenTelemetry transport to the logger", () => {
      const mockTransport = {};
      mockOpenTelemetryTransport.mockImplementation(function (this: unknown) {
        return mockTransport;
      });

      getLogger("test-logger");

      expect(mockLogger.logger.logger.add).toHaveBeenCalledWith(mockTransport);
      expect(mockOpenTelemetryTransport).toHaveBeenCalledWith({
        format: expect.any(Object),
        level: "info",
      });
    });

    test("should use custom log level for transport", () => {
      getLogger("test-logger", { level: "warn" });
      expect(mockOpenTelemetryTransport).toHaveBeenCalledWith({
        format: expect.any(Object),
        level: "warn",
      });
    });

    test("should add current invocation attributes to every log record", () => {
      getLogger("test-logger");

      const [{ format }] = mockOpenTelemetryTransport.mock.calls[0];
      vi.mocked(runtimeHelpers.getRuntimeInvocationAttributes)
        .mockReturnValueOnce({
          "action.activation_id": "first-activation",
          "action.region": "first-region",
        })
        .mockReturnValueOnce({
          "action.activation_id": "second-activation",
          "action.region": "second-region",
        });

      expect(
        format.transform({
          "action.activation_id": "stale-activation",
          message: "first",
        }),
      ).toMatchObject({
        "action.activation_id": "first-activation",
        "action.region": "first-region",
        message: "first",
      });

      expect(format.transform({ message: "second" })).toMatchObject({
        "action.activation_id": "second-activation",
        "action.region": "second-region",
        message: "second",
      });
    });

    test("should handle undefined log level", () => {
      getLogger("test-logger", { level: undefined });
      expect(AioLogger).toHaveBeenCalledWith("test-logger", {
        level: "info",
        provider: "winston",
      });
    });
  });

  describe("setOtelDiagLogger", () => {
    test("should set diagnostics logger with info level", () => {
      setOtelDiagLogger({
        exportLogs: true,
        logLevel: "info",
      });

      expect(AioLogger).toHaveBeenCalledWith("test-action/otel-diagnostics", {
        level: "info",
        logSourceAction: false,
        provider: "winston",
      });

      expect(diag.setLogger).toHaveBeenCalledWith(mockLogger, {
        logLevel: DiagLogLevel.INFO,
      });

      expect(diag.info).toHaveBeenCalledWith(
        "OpenTelemetry diagnostics logger set successfully",
      );
    });

    test("should use custom logger name", () => {
      setOtelDiagLogger({
        exportLogs: true,
        loggerName: "custom-diag-logger",
        logLevel: "debug",
      });

      expect(AioLogger).toHaveBeenCalledWith("custom-diag-logger", {
        level: "debug",
        logSourceAction: false,
        provider: "winston",
      });
    });

    test.each([
      // { input: "none" as const, aioLevel: undefined, diagLevel: "NONE" },
      { aioLevel: "verbose", diagLevel: "ALL", input: "all" as const },
      { aioLevel: "error", diagLevel: "ERROR", input: "error" as const },
      { aioLevel: "warn", diagLevel: "WARN", input: "warn" as const },
      { aioLevel: "info", diagLevel: "INFO", input: "info" as const },
      { aioLevel: "debug", diagLevel: "DEBUG", input: "debug" as const },
      {
        aioLevel: "verbose",
        diagLevel: "VERBOSE",
        input: "verbose" as const,
      },
    ] as const)(
      "should map log level '$input' to equivalent AIO logger level '$aioLevel' and OpenTelemetry diagnostics logger level '$diagLevel'   correctly",
      ({ input, aioLevel, diagLevel }) => {
        setOtelDiagLogger({
          exportLogs: true,
          logLevel: input,
        });

        expect(AioLogger).toHaveBeenCalledWith(
          expect.any(String),
          expect.objectContaining({ level: aioLevel }),
        );

        expect(diag.setLogger).toHaveBeenCalledWith(expect.anything(), {
          logLevel: DiagLogLevel[diagLevel as keyof typeof DiagLogLevel],
        });
      },
    );

    test.each(["info", "warn", "error"] as const)(
      "should export diagnostic logs for level '%s'",
      (level) => {
        setOtelDiagLogger({
          exportLogs: true,
          logLevel: level,
        });

        expect(mockLogger.logger.logger.add).toHaveBeenCalled();
        expect(mockOpenTelemetryTransport).toHaveBeenCalledWith({
          format: expect.any(Object),
          level,
        });
      },
    );

    test("should export logs only for info, warn, and error levels", () => {
      // Test info level - should export
      setOtelDiagLogger({
        exportLogs: true,
        logLevel: "info",
      });

      expect(mockLogger.logger.logger.add).toHaveBeenCalled();
      expect(mockOpenTelemetryTransport).toHaveBeenCalledWith({
        format: expect.any(Object),
        level: "info",
      });

      vi.clearAllMocks();

      // Test debug level - should not export
      setOtelDiagLogger({
        exportLogs: true,
        logLevel: "debug",
      });

      expect(mockLogger.logger.logger.add).toHaveBeenCalled();
      expect(mockOpenTelemetryTransport).toHaveBeenCalledWith({
        format: expect.any(Object),
        level: "info",
      });
    });

    test("should not add transport when exportLogs is false", () => {
      setOtelDiagLogger({
        exportLogs: false,
        logLevel: "info",
      });

      expect(mockLogger.logger.logger.add).not.toHaveBeenCalled();
    });

    test("should handle errors when setting logger", () => {
      vi.mocked(diag.setLogger).mockImplementation(() => {
        throw new Error("Failed to set logger");
      });

      setOtelDiagLogger({
        exportLogs: true,
        logLevel: "info",
      });

      expect(diag.error).toHaveBeenCalledWith(
        "Failed to set the telemetry diagnostics",
        expect.any(Error),
      );
    });

    test("should not call ensureSdkInitialized for diagnostics logger", () => {
      setOtelDiagLogger({
        exportLogs: true,
        logLevel: "info",
      });

      expect(sdkModule.ensureSdkInitialized).not.toHaveBeenCalled();
    });
  });
});
