import { Request, Response, NextFunction } from "express";
import Logging from "@hibas123/nodelogging";
import {
   types
} from "util";
import RequestError, { HttpStatusCode } from "../../helper/request_error.js";

export enum Types {
   STRING,
   NUMBER,
   BOOLEAN,
   EMAIL,
   OBJECT,
   DATE,
   ARRAY,
   ENUM,
}

function isEmail(value: any): boolean {
   return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
      value
   );
}

export interface CheckObject {
   type: Types;
   query?: boolean;
   optional?: boolean;

   /**
    * Only when Type.ENUM
    *
    * values to check before
    */
   values?: string[];

   /**
    * Only when Type.STRING
    */
   notempty?: boolean; // Only STRING
}

export interface Checks {
   [index: string]: CheckObject; // | Types
}

// req: Request, res: Response, next: NextFunction
export default function (fields: Checks, noadditional = false) {
   return (req: Request, res: Response, next: NextFunction) => {
      let errors: { message: string; field: string }[] = [];

      function check(data: any, field_name: string, field: CheckObject) {
         if (data !== undefined && data !== null) {
            switch (field.type) {
               case Types.STRING:
                  if (typeof data === "string") {
                     if (!field.notempty) return;
                     if (data !== "") return;
                  }
                  break;
               case Types.NUMBER:
                  if (typeof data == "number") return;
                  break;
               case Types.EMAIL:
                  if (isEmail(data)) return;
                  break;
               case Types.BOOLEAN:
                  if (typeof data == "boolean") return;
                  break;
               case Types.OBJECT:
                  if (typeof data == "object") return;
                  break;
               case Types.ARRAY:
                  if (Array.isArray(data)) return;
                  break;
               case Types.DATE:
                  if (types.isDate(data)) return;
                  break;
               case Types.ENUM:
                  if (typeof data == "string") {
                     if (field.values.indexOf(data) >= 0) return;
                  }
                  break;
               default:
                  Logging.error(
                     `Invalid type to check: ${field.type} ${Types[field.type]}`
                  );
            }
            errors.push({
               message: res.__(
                  "Field {{field}} has wrong type. It should be from type {{type}}",
                  { field: field_name, type: Types[field.type].toLowerCase() }
               ),
               field: field_name,
            });
         } else {
            if (!field.optional)
               errors.push({
                  message: res.__("Field {{field}} is not defined", {
                     field: field_name,
                  }),
                  field: field_name,
               });
         }
      }

      for (let field_name in fields) {
         let field = fields[field_name];
         let data = fields[field_name].query
            ? req.query[field_name]
            : req.body[field_name];
         check(data, field_name, field);
      }

      if (noadditional) {
         //Checks if the data given has additional parameters
         let should = Object.keys(fields);
         should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body
         let has = Object.keys(req.body);

         has.every((e) => {
            if (should.indexOf(e) >= 0) {
               return true;
            } else {
               errors.push({
                  message: res.__("Field {{field}} should not be there", {
                     field: e,
                  }),
                  field: e,
               });
               return false;
            }
         });
      }

      if (errors.length > 0) {
         let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
         next(err);
      } else next();
   };
}
