import { AbilitiesType } from '~/types/interfaces'
import {AnyJson} from "~/types/data"
import _ from "lodash"

// TODO: peut-être passer ces constantes dans la config?
const rolesByFunction: Array<string> = [
  'ROLE_SUPER_ADMIN',
  'ROLE_ADMIN',
  'ROLE_ADMIN_CORE',
  'ROLE_ADMINISTRATIF_MANAGER',
  'ROLE_ADMINISTRATIF_MANAGER_CORE',
  'ROLE_PEDAGOGICS_MANAGER',
  'ROLE_PEDAGOGICS_MANAGER_CORE',
  'ROLE_FINANCIAL_MANAGER',
  'ROLE_FINANCIAL_MANAGER_CORE',
  'ROLE_CA',
  'ROLE_CA_CORE',
  'ROLE_STUDENT',
  'ROLE_STUDENT_CORE',
  'ROLE_TEACHER',
  'ROLE_TEACHER_CORE',
  'ROLE_MEMBER',
  'ROLE_MEMBER_CORE',
  'ROLE_OTHER',
  'ROLE_OTHER_CORE'
]

const rolesToChange: Array<string> = [
  'ROLE_GENERAL_CONFIG',
  'ROLE_GENERAL_CONFIG_VIEW',
  'ROLE_TAGG_ADVANCED',
  'ROLE_TAGG_ADVANCED_VIEW',
  'ROLE_PEDAGOGICS_ADMINISTRATION',
  'ROLE_PEDAGOGICS_ADMINISTRATION_VIEW',
  'ROLE_PEDAGOGICS_SEIZURE',
  'ROLE_PEDAGOGICS_SEIZURE_VIEW',
  'ROLE_BILLINGS_ADMINISTRATION',
  'ROLE_BILLINGS_ADMINISTRATION_VIEW',
  'ROLE_BILLINGS_SEIZURE',
  'ROLE_BILLINGS_SEIZURE_VIEW',
  'ROLE_ONLINEREGISTRATION_ADMINISTRATION',
  'ROLE_ONLINEREGISTRATION_ADMINISTRATION_VIEW'
]

const actions = ['VIEW', 'REFERENCE', 'CORE']

const actionMap: AnyJson = {
  '': 'manage',
  'VIEW': 'read',
  'REFERENCE': null,
  'CORE': null,
}

interface Role {
  subject: string
  action: 'VIEW' | 'CORE' | 'REFERENCE' | ''
}

/**
 * Classe permettant de mener des opérations sur les rôles
 */
class RoleUtils {
  /**
   * Teste si une personne possède un profil suivant ses rôles
   *
   * @param {string} profileName
   * @param {Array<string>} roles
   * @return {boolean}
   */
  static isA (profileName: string, roles: Array<string>): boolean {
    profileName = profileName.toUpperCase()
    if (!profileName.match(/^[A-Z_]+$/)) {
      throw new Error('invalid role name')
    }
    // TODO: actuellement, passer un profil ne correspondant à aucun rôle ne lèvera aucune erreur, et se contentera de
    //       retourner false; ce serait pas mal de lever une erreur, ce ne serait pas normal de demander un rôle inexistant
    return roles.includes('ROLE_' + profileName + '_CORE')
  }

  /**
   * Filtre les rôles afin d'en exclure les "Roles fonctions"
   *
   * @param {Array<string>} roles
   * @return {Array<string>}
   */
  static filterFunctionRoles (roles: Array<string>): Array<string> {
    return roles.filter((role) => {
      return !rolesByFunction.includes(role)
    })
  }

  /**
   * Parse une chaine de caractère décrivant un rôle applicatif
   *
   * @param role
   */
  static parseRole(role: string): Role {
    const parts = role.split('_')

    if (parts[0] !== 'ROLE') {
      throw new Error('can not parse role')
    }
    parts.shift()

    let action: 'VIEW' | 'CORE' | 'REFERENCE' | '' = ''
    if (actions.includes(parts.at(-1) ?? '')) {
      // @ts-ignore
      action = parts.pop() ?? ''
    }

    const subject = parts.join('-')

    return { subject, action }
  }

  static roleToString(role: Role) {
    //  TODO: est-ce qu'il faut retransformer les - en _ ?  (si oui, attention à maj les tests)
    return ['ROLE', role.subject, role.action].filter((s: string) => s !== null && s.length > 0).join('_')
  }

  /**
   * Construit une habilité à partir du rôle en paramètre.
   * Retourne null si le role ne donne droit à aucune habilité
   * @param role
   */
  static roleToAbility(role: Role): AbilitiesType | null {
    const mappedAction = actionMap[role.action]
    if (mappedAction === null) {
      return null
    }

    return {
      action: mappedAction,
      subject: role.subject.toLowerCase()
    }
  }

  /**
   * On transforme les ROLES Symfony en Abilities
   *
   * Ex:
   *
   *    "ROLE_ORGANIZATION"  =>  { subject: 'organization', action: 'manage'}
   *    "ROLE_PLACE_VIEW"    =>  { subject: 'place', action: 'read'}
   *
   * @param {Array<string>} roles
   * @return {Array<AbilitiesType>}
   */
  static rolesToAbilities (roles: Array<string>): [] | Array<AbilitiesType> {
    const abilities:Array<AbilitiesType> = []

    _.each(roles, (role: string) => {
      const parsed: Role | null= RoleUtils.parseRole(role)
      const ability = RoleUtils.roleToAbility(parsed)

      // @ts-ignore
      if (ability !== null && ability.subject && typeof ability.action !== 'undefined') {
        abilities.push(ability)
      }
    })

    return abilities
  }
}

export default RoleUtils
