import { _optionalChain } from '@sentry/utils';
import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi';
import { defineIntegration, getClient, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SDK_VERSION, getIsolationScope, getDefaultIsolationScope, captureException } from '@sentry/core';
import { logger } from '@sentry/utils';
import { DEBUG_BUILD } from '../../../debug-build.js';
import { generateInstrumentOnce } from '../../../otel/instrument.js';
import { ensureIsWrapped } from '../../../utils/ensureIsWrapped.js';

const INTEGRATION_NAME = 'Hapi';

const instrumentHapi = generateInstrumentOnce(INTEGRATION_NAME, () => new HapiInstrumentation());

const _hapiIntegration = (() => {
  return {
    name: INTEGRATION_NAME,
    setupOnce() {
      instrumentHapi();
    },
  };
}) ;

/**
 * Hapi integration
 *
 * Capture tracing data for Hapi.
 * If you also want to capture errors, you need to call `setupHapiErrorHandler(server)` after you set up your server.
 */
const hapiIntegration = defineIntegration(_hapiIntegration);

function isErrorEvent(event) {
  return event && (event ).error !== undefined;
}

function sendErrorToSentry(errorData) {
  captureException(errorData, {
    mechanism: {
      type: 'hapi',
      handled: false,
      data: {
        function: 'hapiErrorPlugin',
      },
    },
  });
}

const hapiErrorPlugin = {
  name: 'SentryHapiErrorPlugin',
  version: SDK_VERSION,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  register: async function (serverArg) {
    const server = serverArg ;

    server.events.on({ name: 'request', channels: ['error'] }, (request, event) => {
      if (getIsolationScope() !== getDefaultIsolationScope()) {
        const route = request.route;
        if (route && route.path) {
          getIsolationScope().setTransactionName(`${_optionalChain([route, 'access', _ => _.method, 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]) || 'GET'} ${route.path}`);
        }
      } else {
        DEBUG_BUILD &&
          logger.warn('Isolation scope is still the default isolation scope - skipping setting transactionName');
      }

      if (isErrorEvent(event)) {
        sendErrorToSentry(event.error);
      }
    });
  },
};

/**
 * Add a Hapi plugin to capture errors to Sentry.
 */
async function setupHapiErrorHandler(server) {
  await server.register(hapiErrorPlugin);

  // Sadly, middleware spans do not go through `requestHook`, so we handle those here
  // We register this hook in this method, because if we register it in the integration `setup`,
  // it would always run even for users that are not even using hapi
  const client = getClient();
  if (client) {
    client.on('spanStart', span => {
      addHapiSpanAttributes(span);
    });
  }

  // eslint-disable-next-line @typescript-eslint/unbound-method
  ensureIsWrapped(server.register, 'hapi');
}

function addHapiSpanAttributes(span) {
  const attributes = spanToJSON(span).data || {};

  // this is one of: router, plugin, server.ext
  const type = attributes['hapi.type'];

  // If this is already set, or we have no Hapi span, no need to process again...
  if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) {
    return;
  }

  span.setAttributes({
    [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.hapi',
    [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.hapi`,
  });
}

export { hapiErrorPlugin, hapiIntegration, instrumentHapi, setupHapiErrorHandler };
