var {
  _optionalChain
} = require('@sentry/utils');

Object.defineProperty(exports, '__esModule', { value: true });

const api = require('@opentelemetry/api');
const instrumentationHttp = require('@opentelemetry/instrumentation-http');
const core = require('@sentry/core');
const opentelemetry = require('@sentry/opentelemetry');
const instrument = require('../../otel/instrument.js');
const addOriginToSpan = require('../../utils/addOriginToSpan.js');
const getRequestUrl = require('../../utils/getRequestUrl.js');
const SentryHttpInstrumentation = require('./SentryHttpInstrumentation.js');

const INTEGRATION_NAME = 'Http';

const INSTRUMENTATION_NAME = '@opentelemetry_sentry-patched/instrumentation-http';

const instrumentSentryHttp = instrument.generateInstrumentOnce

(`${INTEGRATION_NAME}.sentry`, options => {
  return new SentryHttpInstrumentation.SentryHttpInstrumentation({
    breadcrumbs: _optionalChain([options, 'optionalAccess', _ => _.breadcrumbs]),
    ignoreOutgoingRequests: _optionalChain([options, 'optionalAccess', _2 => _2.ignoreOutgoingRequests]),
  });
});

const instrumentOtelHttp = instrument.generateInstrumentOnce(INTEGRATION_NAME, config => {
  const instrumentation = new instrumentationHttp.HttpInstrumentation(config);

  // We want to update the logger namespace so we can better identify what is happening here
  try {
    instrumentation['_diag'] = api.diag.createComponentLogger({
      namespace: INSTRUMENTATION_NAME,
    });
    // @ts-expect-error We are writing a read-only property here...
    instrumentation.instrumentationName = INSTRUMENTATION_NAME;
  } catch (e) {
    // ignore errors here...
  }

  return instrumentation;
});

/**
 * Instrument the HTTP and HTTPS modules.
 */
const instrumentHttp = (options = {}) => {
  // This is the "regular" OTEL instrumentation that emits spans
  if (options.spans !== false) {
    const instrumentationConfig = getConfigWithDefaults(options);
    instrumentOtelHttp(instrumentationConfig);
  }

  // This is the Sentry-specific instrumentation that isolates requests & creates breadcrumbs
  // Note that this _has_ to be wrapped after the OTEL instrumentation,
  // otherwise the isolation will not work correctly
  instrumentSentryHttp(options);
};

const _httpIntegration = ((options = {}) => {
  return {
    name: INTEGRATION_NAME,
    setupOnce() {
      instrumentHttp(options);
    },
  };
}) ;

/**
 * The http integration instruments Node's internal http and https modules.
 * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
 */
const httpIntegration = core.defineIntegration(_httpIntegration);

/**
 * Determines if @param req is a ClientRequest, meaning the request was created within the express app
 * and it's an outgoing request.
 * Checking for properties instead of using `instanceOf` to avoid importing the request classes.
 */
function _isClientRequest(req) {
  return 'outputData' in req && 'outputSize' in req && !('client' in req) && !('statusCode' in req);
}

/**
 * Detects if an incoming request is a prefetch request.
 */
function isKnownPrefetchRequest(req) {
  // Currently only handles Next.js prefetch requests but may check other frameworks in the future.
  return req.headers['next-router-prefetch'] === '1';
}

function getConfigWithDefaults(options = {}) {
  const instrumentationConfig = {
    ..._optionalChain([options, 'access', _3 => _3.instrumentation, 'optionalAccess', _4 => _4._experimentalConfig]),

    disableIncomingRequestInstrumentation: options.disableIncomingRequestSpans,

    ignoreOutgoingRequestHook: request => {
      const url = getRequestUrl.getRequestUrl(request);

      if (!url) {
        return false;
      }

      const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
      if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) {
        return true;
      }

      return false;
    },

    ignoreIncomingRequestHook: request => {
      // request.url is the only property that holds any information about the url
      // it only consists of the URL path and query string (if any)
      const urlPath = request.url;

      const method = _optionalChain([request, 'access', _5 => _5.method, 'optionalAccess', _6 => _6.toUpperCase, 'call', _7 => _7()]);
      // We do not capture OPTIONS/HEAD requests as transactions
      if (method === 'OPTIONS' || method === 'HEAD') {
        return true;
      }

      const _ignoreIncomingRequests = options.ignoreIncomingRequests;
      if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) {
        return true;
      }

      return false;
    },

    requireParentforOutgoingSpans: false,
    requireParentforIncomingSpans: false,
    requestHook: (span, req) => {
      addOriginToSpan.addOriginToSpan(span, 'auto.http.otel.http');
      if (!_isClientRequest(req) && isKnownPrefetchRequest(req)) {
        span.setAttribute('sentry.http.prefetch', true);
      }

      _optionalChain([options, 'access', _8 => _8.instrumentation, 'optionalAccess', _9 => _9.requestHook, 'optionalCall', _10 => _10(span, req)]);
    },
    responseHook: (span, res) => {
      const client = opentelemetry.getClient();
      if (client && client.getOptions().autoSessionTracking) {
        setImmediate(() => {
          client['_captureRequestSession']();
        });
      }

      _optionalChain([options, 'access', _11 => _11.instrumentation, 'optionalAccess', _12 => _12.responseHook, 'optionalCall', _13 => _13(span, res)]);
    },
    applyCustomAttributesOnSpan: (
      span,
      request,
      response,
    ) => {
      _optionalChain([options, 'access', _14 => _14.instrumentation, 'optionalAccess', _15 => _15.applyCustomAttributesOnSpan, 'optionalCall', _16 => _16(span, request, response)]);
    },
  } ;

  return instrumentationConfig;
}

exports.httpIntegration = httpIntegration;
exports.instrumentOtelHttp = instrumentOtelHttp;
exports.instrumentSentryHttp = instrumentSentryHttp;
