/* eslint-disable no-underscore-dangle */
import AdminJS from 'adminjs';
import { Injectable } from '@nestjs/common';
import { AbstractHttpAdapter } from '@nestjs/core';
import { loadPackage } from '@nestjs/common/utils/load-package.util';

import { AdminModuleOptions } from '../interfaces/admin-module-options.interface';

import { AbstractLoader } from './abstract.loader';

@Injectable()
export class ExpressLoader extends AbstractLoader {
  public register(
    admin: AdminJS,
    httpAdapter: AbstractHttpAdapter,
    options: AdminModuleOptions,
  ) {
    const app = httpAdapter.getInstance();

    loadPackage('express', '@adminjs/nestjs');
    const adminJsExpressjs = loadPackage('@adminjs/express', '@adminjs/nestjs', () =>
      require('@adminjs/express')
    );
    loadPackage('express-formidable', '@adminjs/nestjs');

    let router;

    if ('auth' in options) {
      loadPackage('express-session', '@adminjs/nestjs');
      router = adminJsExpressjs.buildAuthenticatedRouter(
        admin, options.auth, undefined, options.sessionOptions, options.formidableOptions
      );
    } else {
      router = adminJsExpressjs.buildRouter(admin, undefined, options.formidableOptions);
    }

    // This named function is there on purpose. 
    // It names layer in main router with the name of the function, which helps localize
    // admin layer in reorderRoutes() step.
    // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
    app.use(options.adminJsOptions.rootPath, function admin(req, res, next) {
      return router(req, res, next);
    });
    this.reorderRoutes(app);
  }

  private reorderRoutes(app) {
    let jsonParser = [];
    let urlencodedParser = [];
    let admin = [];

    // Nestjs uses bodyParser under the hood which is in conflict with adminjs setup.
    // Due to adminjs-expressjs usage of formidable we have to move body parser in layer tree after adminjs init.
    // Notice! This is not documented feature of express, so this may change in the future. We have to keep an eye on it.
    if (app && app._router && app._router.stack) {
      const jsonParserIndex = app._router.stack.findIndex(
        (layer: { name: string }) => layer.name === 'jsonParser'
      );
      if (jsonParserIndex >= 0) {
        jsonParser = app._router.stack.splice(jsonParserIndex, 1);
      }

      const urlencodedParserIndex = app._router.stack.findIndex(
        (layer: { name: string }) => layer.name === 'urlencodedParser'
      );
      if (urlencodedParserIndex >= 0) {
        urlencodedParser = app._router.stack.splice(urlencodedParserIndex, 1);
      }

      const adminIndex = app._router.stack.findIndex(
        (layer: { name: string }) => layer.name === 'admin'
      );
      if (adminIndex >= 0) {
        admin = app._router.stack.splice(adminIndex, 1)
      }

      // if adminjs-nestjs didn't reorder the middleware
      // the body parser would have come after corsMiddleware
      const corsIndex = app._router.stack.findIndex(
        (layer: { name: string }) => layer.name === 'corsMiddleware',
      )

      // in other case if there is no corsIndex we go after expressInit, because right after that
      // there are nest endpoints.
      const expressInitIndex = app._router.stack.findIndex(
        (layer: { name: string }) => layer.name === 'expressInit',
      )

      const initIndex = (corsIndex >= 0 ? corsIndex : expressInitIndex) + 1;

      app._router.stack.splice(
        initIndex,
        0,
        ...admin,
        ...jsonParser,
        ...urlencodedParser,
      )
    }
  }
}