import { _optionalChain } from '@sentry/utils';
import { hasTracingEnabled, continueTrace, startTransaction, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getCurrentScope, setHttpStatus, getClient, flush, runWithAsyncContext, withScope, getActiveSpan, captureException } from '@sentry/core';
import { isString, extractPathForTransaction, extractRequestData, addRequestDataToTransaction, logger, normalize, isThenable, dropUndefinedKeys } from '@sentry/utils';
import { DEBUG_BUILD } from './debug-build.js';
import { isAutoSessionTrackingEnabled } from './sdk.js';
export { extractRequestData, parseRequest } from './requestDataDeprecated.js';

/**
 * Express-compatible tracing handler.
 * @see Exposed as `Handlers.tracingHandler`
 */
function tracingHandler()

 {
  return function sentryTracingMiddleware(
    req,
    res,
    next,
  ) {
    const options = _optionalChain([getClient, 'call', _ => _(), 'optionalAccess', _2 => _2.getOptions, 'call', _3 => _3()]);

    if (
      !options ||
      options.instrumenter !== 'sentry' ||
      _optionalChain([req, 'access', _4 => _4.method, 'optionalAccess', _5 => _5.toUpperCase, 'call', _6 => _6()]) === 'OPTIONS' ||
      _optionalChain([req, 'access', _7 => _7.method, 'optionalAccess', _8 => _8.toUpperCase, 'call', _9 => _9()]) === 'HEAD'
    ) {
      return next();
    }

    const sentryTrace = req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
    const baggage = _optionalChain([req, 'access', _10 => _10.headers, 'optionalAccess', _11 => _11.baggage]);
    if (!hasTracingEnabled(options)) {
      return next();
    }

    const [name, source] = extractPathForTransaction(req, { path: true, method: true });
    const transaction = continueTrace({ sentryTrace, baggage }, ctx =>
      // TODO: Refactor this to use `startSpan()`
      // eslint-disable-next-line deprecation/deprecation
      startTransaction(
        {
          name,
          op: 'http.server',
          origin: 'auto.http.node.tracingHandler',
          ...ctx,
          data: {
            [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
          },
          metadata: {
            // eslint-disable-next-line deprecation/deprecation
            ...ctx.metadata,
            // The request should already have been stored in `scope.sdkProcessingMetadata` (which will become
            // `event.sdkProcessingMetadata` the same way the metadata here will) by `sentryRequestMiddleware`, but on the
            // off chance someone is using `sentryTracingMiddleware` without `sentryRequestMiddleware`, it doesn't hurt to
            // be sure
            request: req,
          },
        },
        // extra context passed to the tracesSampler
        { request: extractRequestData(req) },
      ),
    );

    // We put the transaction on the scope so users can attach children to it
    // eslint-disable-next-line deprecation/deprecation
    getCurrentScope().setSpan(transaction);

    // We also set __sentry_transaction on the response so people can grab the transaction there to add
    // spans to it later.
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    (res ).__sentry_transaction = transaction;

    res.once('finish', () => {
      // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction
      // closes
      setImmediate(() => {
        addRequestDataToTransaction(transaction, req);
        setHttpStatus(transaction, res.statusCode);
        transaction.end();
      });
    });

    next();
  };
}

/**
 * Backwards compatibility shim which can be removed in v8. Forces the given options to follow the
 * `AddRequestDataToEventOptions` interface.
 *
 * TODO (v8): Get rid of this, and stop passing `requestDataOptionsFromExpressHandler` to `setSDKProcessingMetadata`.
 */
function convertReqHandlerOptsToAddReqDataOpts(
  reqHandlerOptions = {},
) {
  let addRequestDataOptions;

  if ('include' in reqHandlerOptions) {
    addRequestDataOptions = { include: reqHandlerOptions.include };
  } else {
    // eslint-disable-next-line deprecation/deprecation
    const { ip, request, transaction, user } = reqHandlerOptions ;

    if (ip || request || transaction || user) {
      addRequestDataOptions = { include: dropUndefinedKeys({ ip, request, transaction, user }) };
    }
  }

  return addRequestDataOptions;
}

/**
 * Express compatible request handler.
 * @see Exposed as `Handlers.requestHandler`
 */
function requestHandler(
  options,
) {
  // TODO (v8): Get rid of this
  const requestDataOptions = convertReqHandlerOptsToAddReqDataOpts(options);

  const client = getClient();
  // Initialise an instance of SessionFlusher on the client when `autoSessionTracking` is enabled and the
  // `requestHandler` middleware is used indicating that we are running in SessionAggregates mode
  if (client && isAutoSessionTrackingEnabled(client)) {
    client.initSessionFlusher();

    // If Scope contains a Single mode Session, it is removed in favor of using Session Aggregates mode
    const scope = getCurrentScope();
    if (scope.getSession()) {
      scope.setSession();
    }
  }

  return function sentryRequestMiddleware(
    req,
    res,
    next,
  ) {
    if (options && options.flushTimeout && options.flushTimeout > 0) {
      // eslint-disable-next-line @typescript-eslint/unbound-method
      const _end = res.end;
      res.end = function (chunk, encoding, cb) {
        void flush(options.flushTimeout)
          .then(() => {
            _end.call(this, chunk, encoding, cb);
          })
          .then(null, e => {
            DEBUG_BUILD && logger.error(e);
            _end.call(this, chunk, encoding, cb);
          });
      };
    }
    runWithAsyncContext(() => {
      const scope = getCurrentScope();
      scope.setSDKProcessingMetadata({
        request: req,
        // TODO (v8): Stop passing this
        requestDataOptionsFromExpressHandler: requestDataOptions,
      });

      const client = getClient();
      if (isAutoSessionTrackingEnabled(client)) {
        // Set `status` of `RequestSession` to Ok, at the beginning of the request
        scope.setRequestSession({ status: 'ok' });
      }

      res.once('finish', () => {
        const client = getClient();
        if (isAutoSessionTrackingEnabled(client)) {
          setImmediate(() => {
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
            if (client && (client )._captureRequestSession) {
              // Calling _captureRequestSession to capture request session at the end of the request by incrementing
              // the correct SessionAggregates bucket i.e. crashed, errored or exited
              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
              (client )._captureRequestSession();
            }
          });
        }
      });
      next();
    });
  };
}

/** JSDoc */

/** JSDoc */
function getStatusCodeFromResponse(error) {
  const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
  return statusCode ? parseInt(statusCode , 10) : 500;
}

/** Returns true if response code is internal server error */
function defaultShouldHandleError(error) {
  const status = getStatusCodeFromResponse(error);
  return status >= 500;
}

/**
 * Express compatible error handler.
 * @see Exposed as `Handlers.errorHandler`
 */
function errorHandler(options

)

 {
  return function sentryErrorMiddleware(
    error,
    _req,
    res,
    next,
  ) {
    const shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError;

    if (shouldHandleError(error)) {
      withScope(_scope => {
        // The request should already have been stored in `scope.sdkProcessingMetadata` by `sentryRequestMiddleware`,
        // but on the off chance someone is using `sentryErrorMiddleware` without `sentryRequestMiddleware`, it doesn't
        // hurt to be sure
        _scope.setSDKProcessingMetadata({ request: _req });

        // For some reason we need to set the transaction on the scope again
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
        const transaction = (res ).__sentry_transaction ;
        if (transaction && !getActiveSpan()) {
          // eslint-disable-next-line deprecation/deprecation
          _scope.setSpan(transaction);
        }

        const client = getClient();
        if (client && isAutoSessionTrackingEnabled(client)) {
          // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the
          // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only
          // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be
          // running in SessionAggregates mode
          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          const isSessionAggregatesMode = (client )._sessionFlusher !== undefined;
          if (isSessionAggregatesMode) {
            const requestSession = _scope.getRequestSession();
            // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a
            // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within
            // the bounds of a request, and if so the status is updated
            if (requestSession && requestSession.status !== undefined) {
              requestSession.status = 'crashed';
            }
          }
        }

        const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } });
        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
        (res ).sentry = eventId;
        next(error);
      });

      return;
    }

    next(error);
  };
}

/**
 * Sentry tRPC middleware that names the handling transaction after the called procedure.
 *
 * Use the Sentry tRPC middleware in combination with the Sentry server integration,
 * e.g. Express Request Handlers or Next.js SDK.
 */
function trpcMiddleware(options = {}) {
  return function ({ path, type, next, rawInput }) {
    const clientOptions = _optionalChain([getClient, 'call', _12 => _12(), 'optionalAccess', _13 => _13.getOptions, 'call', _14 => _14()]);
    // eslint-disable-next-line deprecation/deprecation
    const sentryTransaction = getCurrentScope().getTransaction();

    if (sentryTransaction) {
      sentryTransaction.updateName(`trpc/${path}`);
      sentryTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
      sentryTransaction.op = 'rpc.server';

      const trpcContext = {
        procedure_type: type,
      };

      if (options.attachRpcInput !== undefined ? options.attachRpcInput : _optionalChain([clientOptions, 'optionalAccess', _15 => _15.sendDefaultPii])) {
        trpcContext.input = normalize(rawInput);
      }

      // TODO: Can we rewrite this to an attribute? Or set this on the scope?
      // eslint-disable-next-line deprecation/deprecation
      sentryTransaction.setContext('trpc', trpcContext);
    }

    function captureIfError(nextResult) {
      if (!nextResult.ok) {
        captureException(nextResult.error, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
      }
    }

    let maybePromiseResult;
    try {
      maybePromiseResult = next();
    } catch (e) {
      captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
      throw e;
    }

    if (isThenable(maybePromiseResult)) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      Promise.resolve(maybePromiseResult).then(
        nextResult => {
          captureIfError(nextResult );
        },
        e => {
          captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
        },
      );
    } else {
      captureIfError(maybePromiseResult );
    }

    // We return the original promise just to be safe.
    return maybePromiseResult;
  };
}

export { errorHandler, requestHandler, tracingHandler, trpcMiddleware };
//# sourceMappingURL=handlers.js.map
