import { tracingChannel } from 'node:diagnostics_channel';
import { defineIntegration, _INTERNAL_captureLog, severityLevelFromString, withScope, addExceptionMechanism, captureException, captureMessage } from '@sentry/core';
import { addInstrumentationConfig } from '../sdk/injectLoader.js';

const SENTRY_TRACK_SYMBOL = Symbol('sentry-track-pino-logger');

const DEFAULT_OPTIONS = {
  error: { levels: [], handled: true },
  log: { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] },
};

function stripIgnoredFields(result) {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { level, time, pid, hostname, ...rest } = result;
  return rest;
}

const _pinoIntegration = defineIntegration((userOptions = {}) => {
  const options = {
    autoInstrument: userOptions.autoInstrument !== false,
    error: { ...DEFAULT_OPTIONS.error, ...userOptions.error },
    log: { ...DEFAULT_OPTIONS.log, ...userOptions.log },
  };

  function shouldTrackLogger(logger) {
    const override = logger[SENTRY_TRACK_SYMBOL];
    return override === 'track' || (override !== 'ignore' && options.autoInstrument);
  }

  return {
    name: 'Pino',
    setup: client => {
      const enableLogs = !!client.getOptions().enableLogs;

      addInstrumentationConfig({
        channelName: 'pino-log',
        // From Pino v9.10.0 a tracing channel is available directly from Pino:
        // https://github.com/pinojs/pino/pull/2281
        module: { name: 'pino', versionRange: '>=8.0.0 < 9.10.0', filePath: 'lib/tools.js' },
        functionQuery: {
          functionName: 'asJson',
          kind: 'Sync',
        },
      });

      const injectedChannel = tracingChannel('orchestrion:pino:pino-log');
      const integratedChannel = tracingChannel('pino_asJson');

      function onPinoStart(self, args, result) {
        if (!shouldTrackLogger(self)) {
          return;
        }

        const resultObj = stripIgnoredFields(result);

        const [captureObj, message, levelNumber] = args;
        const level = self?.levels?.labels?.[levelNumber] || 'info';

        if (enableLogs && options.log.levels.includes(level)) {
          const attributes = {
            ...resultObj,
            'sentry.origin': 'auto.log.pino',
            'pino.logger.level': levelNumber,
          };

          _INTERNAL_captureLog({ level, message, attributes });
        }

        if (options.error.levels.includes(level)) {
          const captureContext = {
            level: severityLevelFromString(level),
          };

          withScope(scope => {
            scope.addEventProcessor(event => {
              event.logger = 'pino';

              addExceptionMechanism(event, {
                handled: options.error.handled,
                type: 'pino',
              });

              return event;
            });

            if (captureObj.err) {
              captureException(captureObj.err, captureContext);
              return;
            }

            captureMessage(message, captureContext);
          });
        }
      }

      injectedChannel.end.subscribe(data => {
        const { self, arguments: args, result } = data ;
        onPinoStart(self, args, JSON.parse(result));
      });

      integratedChannel.end.subscribe(data => {
        const {
          instance,
          arguments: args,
          result,
        } = data ;
        onPinoStart(instance, args, JSON.parse(result));
      });
    },
  };
}) ;

/**
 * Integration for Pino logging library.
 * Captures Pino logs as Sentry logs and optionally captures some log levels as events.
 *
 * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.untrackLogger(logger)`.
 *
 * If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`.
 *
 * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0
 */
const pinoIntegration = Object.assign(_pinoIntegration, {
  trackLogger(logger) {
    if (logger && typeof logger === 'object' && 'levels' in logger) {
      (logger )[SENTRY_TRACK_SYMBOL] = 'track';
    }
  },
  untrackLogger(logger) {
    if (logger && typeof logger === 'object' && 'levels' in logger) {
      (logger )[SENTRY_TRACK_SYMBOL] = 'ignore';
    }
  },
}) ;

export { pinoIntegration };
