/*
 * 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 { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";

import { ensureSdkInitialized } from "#core/sdk";
import {
  getRuntimeActionMetadata,
  getRuntimeInvocationAttributes,
} from "#helpers/runtime";

import type { AioLoggerConfig } from "@adobe/aio-lib-core-logging";
import type WinstonLogger from "@adobe/aio-lib-core-logging/types/WinstonLogger";
import type { DiagLogger } from "@opentelemetry/api";
import type Transport from "winston-transport";
import type { TelemetryDiagnosticsConfig } from "#types";

// If no log level is given, use [INFO].
const DEFAULT_LOG_LEVEL = "info";

type GetLoggerInternalConfig = {
  name: string;
  config?: AioLoggerConfig;
  forceSDKInitialized?: boolean;
  addTransport?: boolean;
  telemetryTransportOptions?: Transport.TransportStreamOptions;
};

const runtimeInvocationAttributesFormat = {
  transform(info) {
    Object.assign(info, getRuntimeInvocationAttributes());
    return info;
  },
} satisfies NonNullable<Transport.TransportStreamOptions["format"]>;

/** @internal */
function __getLoggerInternal({
  name,
  config,
  forceSDKInitialized = true,
  addTransport = true,
  telemetryTransportOptions = {},
}: GetLoggerInternalConfig) {
  if (forceSDKInitialized) {
    ensureSdkInitialized();
  }

  const level = config?.level ?? DEFAULT_LOG_LEVEL;
  const aioLogger = AioLogger(name, {
    ...config,
    level,

    // Provider must be winston, as it's the one compatible with OpenTelemetry.
    provider: "winston",
  });

  if (addTransport) {
    // Hacky, accesses internal API of the AIO logger. Although not likely to change/break.
    // TODO: Better open a PR in: https://github.com/adobe/aio-lib-core-logging to directly support the OpenTelemetry Transport.
    const innerLogger = (aioLogger.logger as WinstonLogger).logger;
    innerLogger.add(
      new OpenTelemetryTransportV3({
        format: runtimeInvocationAttributesFormat,
        level,
        ...telemetryTransportOptions,
      }),
    );
  }

  return aioLogger;
}

/**
 * Gets a logger instance that can export OpenTelemetry logs with the current
 * runtime invocation attributes.
 *
 * @param name - The name of the logger
 * @param config - The configuration for the logger
 *
 * @since 0.1.0
 * @example
 * ```ts
 * const logger = getLogger("my-logger", { level: "debug" });
 * logger.debug("Hello, world!");
 * ```
 */
export function getLogger(name: string, config?: AioLoggerConfig) {
  return __getLoggerInternal({ config, forceSDKInitialized: true, name });
}

/**
 * Sets the global OpenTelemetry diagnostics logger
 * @param config - The configuration for the diagnostics logger.
 */
export function setOtelDiagLogger({
  logLevel: level,
  loggerName,
  exportLogs = true,
}: TelemetryDiagnosticsConfig) {
  // AioLogger and DiagLogger use different names for some log levels.
  const logLevels: Partial<
    Record<TelemetryDiagnosticsConfig["logLevel"], string>
  > = {
    all: "verbose",
    none: undefined,
  };

  const aioLogLevel = logLevels[level] ?? level;
  const { actionName } = getRuntimeActionMetadata();
  const logger = __getLoggerInternal({
    addTransport: exportLogs,
    config: {
      level: aioLogLevel,
      logSourceAction: false,
    },
    forceSDKInitialized: false,
    name: loggerName ?? `${actionName}/otel-diagnostics`,

    // Only use the OpenTelemetry transport (i.e. export diagnostic logs) if the log level is
    // set to info, warn or error. The other levels are too verbose to be exported and may expose
    // irrelevant/sensitive information. All logs will still be visible via `aio rt activation logs <id>`.
    telemetryTransportOptions:
      level === "info" || level === "warn" || level === "error"
        ? { level }
        : { level: "info" },
  });

  // Wrap the logger in a DiagLogger compatible interface.
  const logLevel = level.toUpperCase() as keyof typeof DiagLogLevel;
  try {
    const diagLogger = logger as DiagLogger;
    diag.setLogger(diagLogger, {
      logLevel: DiagLogLevel[logLevel],
    });

    diag.info("OpenTelemetry diagnostics logger set successfully");
  } catch (error) {
    diag.error("Failed to set the telemetry diagnostics", error);
  }
}
