import { MiddlewareConsumer, NestModule } from '@nestjs/common';
import { PluginCommonModule, VendurePlugin } from '@vendure/core';

import { SentryAdminTestResolver } from './api/admin-test.resolver';
import { testApiExtensions } from './api/api-extensions';
import { ErrorTestService } from './api/error-test.service';
import { SENTRY_PLUGIN_OPTIONS } from './constants';
import { SentryApolloPlugin } from './sentry-apollo-plugin';
import { SentryContextMiddleware } from './sentry-context.middleware';
import { SentryErrorHandlerStrategy } from './sentry-error-handler-strategy';
import { SentryService } from './sentry.service';
import { SentryPluginOptions } from './types';

const SentryOptionsProvider = {
    provide: SENTRY_PLUGIN_OPTIONS,
    useFactory: () => SentryPlugin.options,
};

/**
 * @description
 * This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring
 * service with your Vendure server. In addition to capturing errors, it also provides built-in
 * support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as
 * enriching your Sentry events with additional context about the request.
 *
 * ## Pre-requisites
 *
 * This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service.
 *
 * If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project,
 * select the "Node.js" platform and no framework.
 *
 * Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/)
 * which you will need to provide to the plugin.
 *
 * ## Installation
 *
 * Install this plugin as well as the `@sentry/node` package:
 *
 * ```sh
 * npm install --save \@vendure/sentry-plugin \@sentry/node
 * ```
 *
 * ## Configuration
 *
 * Before using the plugin, you must configure it with the DSN provided by Sentry:
 *
 * ```ts
 * import { VendureConfig } from '\@vendure/core';
 * import { SentryPlugin } from '\@vendure/sentry-plugin';
 *
 * export const config: VendureConfig = {
 *     // ...
 *     plugins: [
 *         // ...
 *         // highlight-start
 *         SentryPlugin.init({
 *             dsn: process.env.SENTRY_DSN,
 *             // Optional configuration
 *             includeErrorTestMutation: true,
 *             enableTracing: true,
 *             // you can also pass in any of the options from \@sentry/node
 *             // for instance:
 *             tracesSampleRate: 1.0,
 *         }),
 *         // highlight-end
 *     ],
 * };
 *```
 *
 * ## Tracing
 *
 * This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your
 * GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above.
 *
 * ## Instrumenting your own code
 *
 * You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object
 * just as you would in any Node application. For example:
 *
 * ```ts
 * import * as Sentry from "\@sentry/node";
 *
 * export class MyService {
 *     async myMethod() {
 *          Sentry.setContext('My Custom Context,{
 *              key: 'value',
 *          });
 *     }
 * }
 * ```
 *
 * ## Error test mutation
 *
 * To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API
 * which will throw an error of the type specified in the `errorType` argument. For example:
 *
 * ```graphql
 * mutation CreateTestError {
 *     createTestError(errorType: DATABASE_ERROR)
 * }
 * ```
 *
 * You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear).
 *
 * @docsCategory core plugins/SentryPlugin
 */
@VendurePlugin({
    imports: [PluginCommonModule],
    providers: [SentryOptionsProvider, SentryService, ErrorTestService],
    configuration: config => {
        config.apiOptions.apolloServerPlugins.push(
            new SentryApolloPlugin({
                enableTracing: !!SentryPlugin.options.enableTracing,
            }),
        );
        config.systemOptions.errorHandlers.push(new SentryErrorHandlerStrategy());
        return config;
    },
    adminApiExtensions: {
        schema: () => (SentryPlugin.options.includeErrorTestMutation ? testApiExtensions : undefined),
        resolvers: () => (SentryPlugin.options.includeErrorTestMutation ? [SentryAdminTestResolver] : []),
    },
    exports: [SentryService],
    compatibility: '^3.0.0',
})
export class SentryPlugin implements NestModule {
    static options: SentryPluginOptions = {} as any;

    configure(consumer: MiddlewareConsumer): any {
        consumer.apply(SentryContextMiddleware).forRoutes('*');
    }

    static init(options: SentryPluginOptions) {
        this.options = options;
        return this;
    }
}
