import { Request, Response } from "express";
import {
  PermissionModule,
  permissionModules,
  PermissionObject,
  PermissionSection,
  PermissionString,
  PermissionType,
  permissionTypes,
} from "../type/permissionTypes";
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";

export default class PermissionHelper {
  static can(
    permissions: PermissionObject,
    type: PermissionType | "admin",
    section: PermissionSection,
    module?: PermissionModule
  ) {
    if (type == "admin") return permissions.admin ?? false;
    if (permissions.admin) return true;
    if (
      (!module &&
        permissions[section] != undefined &&
        (permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
      permissions[section]?.all == "*" ||
      permissions[section]?.all?.includes(type)
    )
      return true;
    if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
      return true;
    return false;
  }

  static canSection(
    permissions: PermissionObject,
    type: PermissionType | "admin",
    section: PermissionSection
  ): boolean {
    if (type == "admin") return permissions.admin ?? false;
    if (permissions.admin) return true;
    if (
      permissions[section]?.all == "*" ||
      permissions[section]?.all?.includes(type) ||
      permissions[section] != undefined
    )
      return true;
    return false;
  }

  static passCheckMiddleware(
    requiredPermissions: PermissionType | "admin",
    section: PermissionSection,
    module?: PermissionModule
  ): (req: Request, res: Response, next: Function) => void {
    return (req: Request, res: Response, next: Function) => {
      const permissions = req.permissions;

      if (this.can(permissions, requiredPermissions, section, module)) {
        next();
      } else {
        throw new ForbiddenRequestException(
          `missing permission for ${section}.${module}.${
            Array.isArray(requiredPermissions) ? requiredPermissions.join("|") : requiredPermissions
          }`
        );
      }
    };
  }

  static sectionPassCheckMiddleware(
    requiredPermissions: PermissionType | "admin",
    section: PermissionSection
  ): (req: Request, res: Response, next: Function) => void {
    return (req: Request, res: Response, next: Function) => {
      const permissions = req.permissions;

      if (this.canSection(permissions, requiredPermissions, section)) {
        next();
      } else {
        throw new ForbiddenRequestException(
          `missing permission for ${section}.${module}.${
            Array.isArray(requiredPermissions) ? requiredPermissions.join("|") : requiredPermissions
          }`
        );
      }
    };
  }

  static convertToObject(permissions: Array<PermissionString>): PermissionObject {
    if (permissions.includes("*")) {
      return {
        admin: true,
      };
    }
    let output: PermissionObject = {};
    let splitPermissions = permissions.map((e) => e.split(".")) as Array<
      [PermissionSection, PermissionModule | PermissionType | "*", PermissionType | "*"]
    >;
    for (let split of splitPermissions) {
      if (!output[split[0]]) {
        output[split[0]] = {};
      }
      if (split[1] == "*" || output[split[0]].all == "*") {
        output[split[0]] = { all: "*" };
      } else if (permissionTypes.includes(split[1] as PermissionType)) {
        if (!output[split[0]].all || !Array.isArray(output[split[0]].all)) {
          output[split[0]].all = [];
        }
        const permissionIndex = permissionTypes.indexOf(split[1] as PermissionType);
        const appliedPermissions = permissionTypes.slice(0, permissionIndex + 1);
        output[split[0]].all = appliedPermissions;
      } else {
        if (split[2] == "*" || output[split[0]][split[1] as PermissionModule] == "*") {
          output[split[0]][split[1] as PermissionModule] = "*";
        } else {
          if (
            !output[split[0]][split[1] as PermissionModule] ||
            !Array.isArray(output[split[0]][split[1] as PermissionModule])
          ) {
            output[split[0]][split[1] as PermissionModule] = [];
          }
          const permissionIndex = permissionTypes.indexOf(split[2] as PermissionType);
          const appliedPermissions = permissionTypes.slice(0, permissionIndex + 1);
          output[split[0]][split[1] as PermissionModule] = appliedPermissions;
        }
      }
    }
    return output;
  }

  static convertToStringArray(permissions: PermissionObject): Array<PermissionString> {
    if (permissions.admin) {
      return ["*"];
    }
    let output: Array<PermissionString> = [];
    let sections = Object.keys(permissions) as Array<PermissionSection>;
    for (let section of sections) {
      if (permissions[section].all) {
        let types = permissions[section].all;
        if (types == "*" || types.length == permissionTypes.length) {
          output.push(`${section}.*`);
        } else {
          for (let type of types) {
            output.push(`${section}.${type}`);
          }
        }
      } else {
        let modules = Object.keys(permissions[section]) as Array<PermissionModule>;
        for (let module of modules) {
          let types = permissions[section][module];
          if (types == "*" || types.length == permissionTypes.length) {
            output.push(`${section}.${module}.*`);
          } else {
            for (let type of types) {
              output.push(`${section}.${module}.${type}`);
            }
          }
        }
      }
    }
    return output;
  }

  static getWhatToAdd(before: Array<PermissionString>, after: Array<PermissionString>): Array<PermissionString> {
    return after.filter((permission) => !before.includes(permission));
  }

  static getWhatToRemove(before: Array<PermissionString>, after: Array<PermissionString>): Array<PermissionString> {
    return before.filter((permission) => !after.includes(permission));
  }
}