/**
 * @file Ce fichier contient la classe de l'API du LDAP qui gère les opérations sur les groupes. Elle est destinée à être exportée pour être utilisée par les resolvers.
 * @author hawkspar
 */

import {ldapConfig, userData, categories} from '../internal/config';
import {Basics} from '../internal/basics';
import {Tools} from '../internal/tools';
import ldapEscape from 'ldap-escape';

//------------------------------------------------------------------------------------------------------------------------
// Classes à exporter TBT
//------------------------------------------------------------------------------------------------------------------------

export {userData};

export class User {
    /**
     * @memberof LDAP
     * @class User
     * @classdesc Cette classe est une des deux classes exportables permettant de faire des opérations sur les utilisateurs.
     * @summary Constructeur vide.
     */
    constructor() {}
     
    /**
     * @memberof LDAP
     * @summary Fonction qui renvoit les infos de base relatives à un utilisateur particulier.
     * @desc Cette fonction utilise {@link Tools.peek} avec l'interface {@link userData}.
     * @arg {string} uid - Identifiant de l'utilisateur, supposé valide.
     * @return {Promise(userData)} Informations recueillies au format {@link userData}.
     * @static
     * @async
     */
    static async peek(uid: string) : Promise<userData> {
        try { 
            let data : userData = await Tools.peek<userData>("user", uid, userData);
            for (let cat of categories) { 
                let dn = ldapConfig.user.uid + "=" + ldapEscape.filter("${txt}", { txt: uid }) + "," + ldapConfig.dn.user;
                console.log(ldapConfig.group[cat] + "=" + dn);
                data[cat] = await Basics.searchSingle("group", ldapConfig.group.gid, null, ldapConfig.group[cat] + "=" + dn);
                console.log(data[cat]);
            }
            return data;
        }
        catch(err) {
            throw "Error while peeking a user.";
        }
    }
    
    /**
     * @memberof LDAP
     * @summary Fonction qui retrouve les uid des paxs validant les critères de recherche. Utiliser {@link User.peek} au cas par cas après pour obtenir les vraies infos.
     * @desc Cette fonction utilise {@link Tools.search}.
     * @arg {userData} data - Dictionnaire contenant les données nécessaires à la recherche. Les valeurs sont celles entrées par l'utilisateur et sont par hypothèse
     * comme des sous-parties compactes des valeurs renvoyées. Tous les champs ci-dessous peuvent être indifféremment des listes (par exemple pour chercher un membre
     * de plusieurs groupes) ou des éléments isolés. Si un champ n'est pas pertinent, le mettre à '' ou undefined.
     * @return {Promise(string[])} gids des profils qui "match" les critères proposés.
     * @static
     * @async
     */
    static async search(data: userData) : Promise<string[]> {
        try {
            return Tools.search("user", data);
        }
        catch(err) {
            throw "Erreur lors de la recherche approximative d'un utilisateur.";
        }
    }
    
    /**
     * @memberof LDAP
     * @summary Fonction qui créé un nouvel utilisateur dans le LDAP.
     * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link Tools.add} pour gérer les groupes du nouvel utilisateur.
     * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
     * Cette application permet de rejoindre des groupes en masse pour toute catégorie.
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async create(data: userData): Promise<boolean> {
        // Calcul d'un dictionnaire d'ajout
        let vals = {};

        // uid de base généré à partir de nom et prénom, plus potentiellement promo et un offset
        // MEF mélange de Promise et de fonction standard
        try {
            Tools.generateUid("user",data['givenName'],data['lastName'],data['birthdate']).then(id => { vals[ldapConfig.user.uid]=id; } );
        }
        catch(err) {
            throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur.";
        }

        let uid = vals[ldapConfig.user.uid];

        // Génère une erreur si un champ n'est pas rempli
        for (let key_att in data) {
            // Ecriture de toutes les valeurs uniques
            if (!Array.isArray(data[key_att])) { vals[ldapConfig.user[key_att]]=data[key_att]; }
        }

        // Appel à la fonction de base
        if (!await Basics.add("user", vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; }
        
        for (let key_att in data) {
            // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples
            if (Array.isArray(data[key_att])) {
                // On rajoute chaque valeur en entrée
                data[key_att].forEach(val => {
                    let vals2 = {};
                    vals2[ldapConfig.user[key_att]]=val;
                    Basics.change("user", uid, "add", vals2).then(res => {
                        if (!res) { throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur."; }
                    });
                });
            }
        }
        
        // Certains champs nécessitent de petits calculs
        let vals3={};

        // ldapConfiguration du mot de passe utilisateur
        // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html 
        vals3[ldapConfig.user['password']] = "{CRYPT}"+data['password'];
        
        // Ecriture d'un surnom s'il y a lieu
        if ((data['nickname']!=undefined) && (data['nickname']!='')) {
            vals3[ldapConfig.user['nickname']]=data['nickname'];
        }
        try {
            // Génération id aléatoire unique
            vals3[ldapConfig.user['id']]= await Tools.generateId(ldapConfig.user['id'], "user");
        }
        catch(err) {
            throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur.";
        }

        // Code root
        vals3[ldapConfig.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD');

        // Inscription des valeurs calculées
        if (!await Basics.change("user", uid, "add", vals3)) {
            throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur.";
        }

        ["posixAccount", "shadowAccount", "brUser"].forEach(cst => {
            let val3={};
            vals3[ldapConfig.user['class']]=cst;
            Basics.change("user", uid, "add", vals3).then(res => {
                if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; }
            });
        });

        // Ajout dans les groupes à la catégorie voulue
        for (let cat of categories) {
            for (let gid of data[cat]) {
                Tools.add(uid, gid, cat);
            }
        }
        return true;
    }

    //------------------------------------------------------------------------------------------------------------------------
    // Fonctions de suppression TBT
    //------------------------------------------------------------------------------------------------------------------------
    
    /**
     * @memberof LDAP
     * @summary Fonction qui supprime un utilisateur du LDAP.
     * @desc Cette fonction commence par gérer les groupes du membre puis le supprime entièrement.
     * Appelle {@link LDAP.clear} bien sûr, mais aussi {@link Tools.remove} pour gérer les groupes de l'utilisateur sortant.
     * @arg {string} uid - uid de la victime
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async delete(uid: string): Promise<boolean> {
        try {
            // Gestion des groupes d'abord
            let profil = await User.peek(uid);
            for (let cat of categories) {
                profil[ldapConfig.user[cat]].forEach(async function (gid: string) {
                    // Enlever de la liste des membres
                    let lm = await Tools.get(gid, "group", cat);
                    if (lm.includes(uid)) {
                        // Supprime tous les membres
                        if (!await Basics.change("group", gid, "del", ldapConfig.group[cat])) {
                            throw "Erreur lors de la suppression de tous les membres du groupe.";
                        }
                        // Les rajoute un par un, sauf pour le supprimé
                        lm.forEach(id => {
                            if (id!=uid) {
                                Tools.add(id, gid, cat).then(res => {
                                    if (!res) { throw "Erreur lors du ré-ajout d'un autre membre"; }
                                });
                            }
                        });
                    }
                });
            }
        }
        catch(err) {
            throw "Erreur lors de l'obtention des informations de l'utilisateur à supprimer.";
        }
        // Elimination
        if (!Basics.clear("user", uid)) { throw "Erreur lors de la suppression de l'utilisateur."; }
        return true;
    }

    /**
     * @memberof LDAP
     * @summary Fonction qui édite un utilisateur existant dans le LDAP.
     * @desc Appelle simplement {@link Tools.edit}. Sans effet sur les groupes de l'utilisateur concerné.
     * @arg {userData} data - Dictionnaire des informations utilisateurs
     * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
     * @async
     * @static
     */
    static async edit(data : userData) : Promise<boolean> {
        try {
            return Tools.edit("user",data);
        }
        catch(err) {
            throw "Erreur lors de la modification d'un utilisateur.";
        }
    }
}