import Config                                                       from '../config/Config';
import { StatusCodes }                                              from 'http-status-codes';
import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'express-validator/src/base';
import { BailOptions, ValidationChain }                             from 'express-validator/src/chain';
import GitlabManager                                                from '../managers/GitlabManager';
import express                                                      from 'express';
import logger                                                       from '../shared/logging/WinstonLogger';
import Json5FileValidator                                           from '../shared/helpers/Json5FileValidator';
import ExerciseResultsFile                                          from '../shared/types/Dojo/ExerciseResultsFile';
import ParamsCallbackManager                                        from '../middlewares/ParamsCallbackManager';
import ExerciseManager                                              from '../managers/ExerciseManager';


declare type DojoMeta = Meta & {
    req: express.Request
};

declare type DojoCustomValidator = (input: unknown, meta: DojoMeta) => unknown;

declare type DojoCustomValidatorSchemaOptions = { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: CustomValidator | ValidationChain, options?: CustomValidator }


class DojoValidators {
    private toValidatorSchemaOptions(arg: { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: DojoCustomValidator | ValidationChain, options?: DojoCustomValidator }) {
        // This is a hack to make the types work with req arg as express.Request instead of Request defined by express-validator
        return arg as unknown as DojoCustomValidatorSchemaOptions;
    }

    private getParamValue(req: express.Request, path: string): unknown {
        return 'body' in req && path in req.body ? req.body[path] : req.query[path];
    }

    readonly nullSanitizer = this.toValidatorSchemaOptions({
                                                               options: value => {
                                                                   try {
                                                                       return value === 'null' || value === 'undefined' || value === '' ? null : value;
                                                                   } catch ( error ) {
                                                                       logger.error(`null sanitizer error: ${ error }`);

                                                                       return value;
                                                                   }
                                                               }
                                                           });

    readonly jsonSanitizer = this.toValidatorSchemaOptions({
                                                               options: value => {
                                                                   try {
                                                                       return JSON.parse(value as string);
                                                                   } catch ( e ) {
                                                                       return value;
                                                                   }
                                                               }
                                                           });

    readonly templateUrlValidator = this.toValidatorSchemaOptions({
                                                                      bail        : true,
                                                                      errorMessage: 'Template doesn\'t exist or you don\'t have access to it',
                                                                      options     : (_value, {
                                                                          req,
                                                                          path
                                                                      }) => {
                                                                          return new Promise((resolve, reject) => {
                                                                              const template = this.getParamValue(req, path) as string;
                                                                              if ( template ) {
                                                                                  GitlabManager.checkTemplateAccess(template, req).then(templateAccess => {
                                                                                      templateAccess !== StatusCodes.OK ? reject() : resolve(true);
                                                                                  });
                                                                              }
                                                                              resolve(true);
                                                                          });
                                                                      }
                                                                  });

    readonly templateUrlSanitizer = this.toValidatorSchemaOptions({
                                                                      options: (value, {
                                                                          req,
                                                                          path
                                                                      }) => {
                                                                          try {
                                                                              const template = this.getParamValue(req, path);
                                                                              if ( template ) {
                                                                                  const gitlabUrlWithCredentials = Config.gitlab.urls[0].replace(/^([a-z]{3,5}:\/{2})?(.*)/, `$1${ Config.gitlab.account.username }:${ Config.gitlab.account.token }@$2`);
                                                                                  return `${ gitlabUrlWithCredentials }${ template }.git`;
                                                                              } else {
                                                                                  return Config.assignment.default.template;
                                                                              }
                                                                          } catch ( error ) {
                                                                              logger.error(`Template url sanitizer error: ${ error }`);

                                                                              return value;
                                                                          }
                                                                      }
                                                                  });

    readonly exerciseResultsValidator = this.toValidatorSchemaOptions({
                                                                          bail        : true,
                                                                          errorMessage: 'Results: not provided or invalid format',
                                                                          options     : (_value, {
                                                                              req,
                                                                              path
                                                                          }) => {
                                                                              return new Promise((resolve, reject) => {
                                                                                  const results = this.getParamValue(req, path) as string;
                                                                                  if ( results ) {
                                                                                      Json5FileValidator.validateFile(ExerciseResultsFile, results, false).isValid ? resolve(true) : reject();
                                                                                  } else {
                                                                                      reject();
                                                                                  }
                                                                              });
                                                                          }
                                                                      });

    readonly exerciseIdOrUrlValidator = this.toValidatorSchemaOptions({
                                                                          bail        : true,
                                                                          errorMessage: 'ExerciseIdOrUrl: not provided or invalid',
                                                                          options     : (_value, {
                                                                              req,
                                                                              path
                                                                          }) => {
                                                                              return new Promise((resolve, reject) => {
                                                                                  const exerciseIdOrUrl = this.getParamValue(req, path) as string;
                                                                                  if ( exerciseIdOrUrl ) {
                                                                                      ParamsCallbackManager.initBoundParams(req);

                                                                                      ExerciseManager.get(exerciseIdOrUrl).then(exercise => {
                                                                                          req.boundParams.exercise = exercise;

                                                                                          exercise !== undefined ? resolve(true) : reject();
                                                                                      }).catch(() => {
                                                                                          reject();
                                                                                      });
                                                                                  } else {
                                                                                      reject();
                                                                                  }
                                                                              });
                                                                          }
                                                                      });
}


export default new DojoValidators();