import * as Sentry from '@sentry/node';
import * as Tracing from '@sentry/tracing';
import cors from 'cors';
import express, { Express } from 'express';
import rateLimit from 'express-rate-limit';
import userAgent from 'express-useragent';
import env, { isTesting } from '../env';
import hooks from './hooks';
import jobs from './jobs';
import router from './routing';
import metricsService from './services/external/metrics.service';

const RATE_LIMIT_MINUTES = 5;
const RATE_LIMIT_REQUESTS = 150;

class API {
  express: Express;

  constructor() {
    this.express = express();

    if (!isTesting()) {
      this.setupServices();
    }

    this.setupMiddlewares();
    this.setupRouting();
  }

  setupMiddlewares() {
    this.express.use(Sentry.Handlers.requestHandler());
    this.express.use(Sentry.Handlers.tracingHandler());

    this.express.set('trust proxy', 1);

    this.express.use(userAgent.express());
    this.express.use(express.json());
    this.express.use(express.urlencoded({ extended: true }));
    this.express.use(cors());

    // Limits 500 requests per ip, every 5 minutes
    this.express.use(
      rateLimit({
        windowMs: RATE_LIMIT_MINUTES * 60 * 1000,
        max: RATE_LIMIT_REQUESTS
      })
    );

    // Register each http request for metrics processing
    this.express.use((req, res, next) => {
      const endTimer = metricsService.trackHttpRequestStarted();

      res.on('finish', () => {
        if (!req.route) return;

        const route = `${req.baseUrl}${req.route.path}`;
        if (route === '/api/metrics/') return;

        const status = res.statusCode;
        const method = req.method;

        // Browsers block sending a custom user agent, so we're sending a custom header in our webapp
        const userAgentHeader = req.get('X-User-Agent') || req.get('User-Agent');
        const userAgent = metricsService.reduceUserAgent(userAgentHeader, req.useragent);

        metricsService.trackHttpRequestEnded(endTimer, route, status, method, userAgent);
      });

      next();
    });
  }

  setupRouting() {
    this.express.use('/api', router);
  }

  setupServices() {
    jobs.init();
    hooks.setup();

    Sentry.init({
      dsn: env.SENTRY_DSN,
      tracesSampleRate: 0.01,
      integrations: [
        new Sentry.Integrations.Http({ tracing: true }),
        new Tracing.Integrations.Express({ app: this.express })
      ]
    });
  }
}

export default new API().express;