/**
 * @file Ce fichier génère le schéma GraphQL. C'est ici que les requêtes GraphQl sont résolues.
 * @author akka vodol
*/
import knex from '../../db/knex_router';
import { rens, idTOL, listGroups, listMembers, listAdmins } from '../ldap/ldap_data';
import { makeExecutableSchema } from 'graphql-tools';
import { request } from 'https';
import { assertBinaryExpression } from 'babel-types';

import { Group, User } from './user_and_group';

const typeDefs = `

    type Query {
        allGroups: [Group]
        group(uid: ID) : Group
        user(uid: ID) : [User]
    }

    type Mutation {
        asAdmin(groupid: String): AdminMutation
        asMember(groupid: String): MemberMutation
        asViewer(groupid: String): ViewerMutation

        createGroup(
            uid: ID = null
            name: String
            website: String
            description: String
            school: String
            parentuid : String
        ): Group
    }

    type AdminMutation {

        createSubgroup(
            uid: ID = null, 
            name: String,
            website: String,
            description: String,
            school: String,
        ): Group

        addUser(userid : String): User
        removeUser(userid : String): User
        addAdmin(userid : String): User
        removeAdmin(userid : String): User

        editGroup(
            name: String,
            website: String,
            description: String,
            school: String,
        ): Group

    }

    type MemberMutation {
        leave: Group
    }

    type ViewerMutation {
        requestJoin: Group
    }

`;

/**
 * @summary Renvoie tous les utilisateurs ayant des droits d'administrateur sur un groupe.
 * @desc Les utilisateurs qui ont un droit d'administrateur sur un groupe sont ses administrateurs et les utilisateurs ayant droit d'admin sur son parent
 * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. 
 * @return {Promise} Retour de requête knex. Promise qui renvera une liste de tous les utilisateurs ayant droit d'admin sur le groupe
 */
const getUsersWithAdminRights = (user, groupUID) => {
    return listAdmins(user, groupUID).then(adminList => {
        if(typeof adminList == "undefined")
            return undefined;
        else
            return knex('groups').select('parentuid').where('uid', groupUID).then(reqResult => {
                if(reqResult[0].parentuid)
                    return getUsersWithAdminRights(user, reqResult[0].parentuid).then(parentAdmins => {
                        return adminList.concat(parentAdmins);
                    });
                else
                    return adminList;
            });
    });
};

/**
 * @summary teste si un utilisateur a des droits
 * @desc Cette fonction effectue une requête knex. Elle gère l'arête de parenté.
 * @arg {Object} user - Objet contenant un attribut *uid* de type *string*. User représente l'utilisateur qui a effectué la requête. 
 * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire.
 */
const hasAdminRights = (user, groupuid) => {
    return getUsersWithAdminRights(user, groupuid).then(adminList => {
        return (typeof adminList != "undefined"  && adminList.indexOf(user.uid) != -1);
    });
};

/**
 * @summary Renvoie tous les groupes visibles par l'utilisateur user
 * @desc Cette fonction effectue une requête knex. Elle gère l'arête de parenté.
 * @arg {Object} user - Objet contenant un attribut *uid* de type *string*. User représente l'utilisateur qui a effectué la requête. 
 * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire.
 */
const getAllVisibleGroups = (user) => {

    return listGroups(user, user.uid).then(group_ids => {
        if(typeof group_ids == "undefined")
            throw "invalid user";
        var membered_groups = qb => qb.select().from('groups').whereIn('uid', group_ids.concat(['kes'])); 
        /* membered_groups est un callback qui reçoit un argument qb représentant un objet de requête (un truc qui s'utilise comme knex())
        *  et effectue dessus la requête pour obtenir une table de tous les groupes dont user est membre
        *  with est ensuite utilisé pour donner à ce retour de requête l'alias 'membered_groups'.
        *  Cette table peut ainsi être JOIN avec la table groups. La condition de Join est donnée selon une syntaxe recopiée depuis la Doc Knex
        *  On récupère tous les groupes tels que le groupe est dans membered_groups ou le parent est dans membered_groups
        */ 
        return knex.with('membered_groups', membered_groups).distinct('groups.*').select().from('groups').innerJoin('membered_groups', 
            function() {
                this.on('groups.uid', '=', 'membered_groups.uid').orOn('groups.parentuid', '=', 'membered_groups.uid');
            }
        );
    });
};

/**
 * @summary Renvoie un unique groupe, ssi ce groupe est visible par l'utilisateur
 * @desc Actuellement, la fonction appelle *getAllVisibleGroups* et restreint au groupe demandé. Cette fonction peut être implémentée de manière plus efficace et plus chiante.
 * @arg {Object} user - Objet contenant un attribut *uid* de type *string*. User représente l'utilisateur qui a effectué la requête. 
 * @arg {String} uid - uid du groupe que l'on veut voire. 
 * @return {Promise} Retour de requête knex. Le groupe demandé, si l'utilisateur a le droit de la voire.
 */
const getGroupIfVisible = (user, uid) => {
    return /*getAllVisibleGroups(user)*/knex('groups').where('groups.uid', uid).then(res => {
        return res[0];
    });
};

/**
 * @summary Attribue un UID qui n'a pas encore été utilisé à un groupe
 * @desc RASifie le string initialUID si necessaire (ramené à de l'ASCCI sans espace), puis si l'uid est deja pris rajoute un n a la fin et reteste
 * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. 
 * @return {Promise} Retour de requête knex. Promise qui renvera une liste de tous les utilisateurs ayant droit d'admin sur le groupe
 */
const getAvailableGroupUID = (initialUID) => {
    let rasUID = initialUID.replace(' ', '_').replace(/\W/g, '').toLowerCase();
    return knex.from('groups').where('uid', rasUID).then(res =>{
        if(res.length == 0){
            return(rasUID);
        }else{
            return(getAvailableGroupUID(rasUID + 'n'));
        }
    });
};

/**
 * @summary Créé un groupe si les arguments sont tous valides et l'utilisateur est authorisé
 * @desc Les arguments doivent être valides, sauf pour uid. Une clé uid valide sera générée dans tous les cas. 
 * On teste si l'utilisateur qui envoie la requête a des droits d'admin sur le parent du groupe qui doit être créé, avec la fonction *getUsersWithAdminRights*
 * Si un argument est invalide ou si l'utilisateur n'a pas les droits, la fonction renvoie une erreur
 * @arg {Object} user - L'utilisateur qui effectue la requête. 
 * @arg {Object} args - Les arguments envoyés à la mutation. Cf le schéma GraphQL 
 * @return {Promise} Retour de requête knex. Le groupe qui vient d'être créé. En cas d'echec, renvoie une erreur.
 */
const createGroupIfLegal = (user, args) => {
    if(typeof args.parentuid != 'string')
        throw "Illegal argument : parentuid must be a string";
    return getUsersWithAdminRights(user, args.parentuid).then( admin_list => {
        if(typeof admin_list == undefined) //Cela arrive si arg.parentuid n'est pas un id de groupe valable (ou éventuellement si Chevalier fait de la merde)
            throw "invalid argument : no group with id " + args.parentuid;
        admin_list = admin_list.concat(['anatole.romon']); // pour les besoins des tests, anatole romon a tout les droits
        if(admin_list.indexOf(user.uid) == -1)
            throw "illegal request : you must have admin rights over a group to create a subgroup of that group";
        if(typeof args.uid != "string")
            args.uid = args.name;
        return(getAvailableGroupUID(args.uid).then(rasUID => {
            // TODO : appeller une fonction de ldap_data pour y créer un groupe.
            return knex('groups').insert({
                uid : rasUID,
                parentuid : args.parentuid,
                createdAt : knex.fn.now(),
                updatedAt : this.createdAt,
                name : args.name,
                website : args.website,
                description : args.description,
                school : args.school
            }).then( res => {
                return getGroupIfVisible(user, rasUID);
            });
        })); 
    });
};

/** 
 * @description Résolveurs des différentes requêtes GraphQL
*/
const resolvers = {
    Query: {
        allGroups: (obj, args, context) => {
            return getAllVisibleGroups(context.user);
        },
        group: (obj, args, context) => {
            return getGroupIfVisible(context.user, args.uid);
        },
        user: (obj, args, context) => {
            const refactorer = (data) => {
                return {
                    uid: args.uid,
                    lastName: data.sn,
                    givenName: data.givenName,
                    birthdate: data.brBirthdate,
                    groups: data.brMemberOf,
                    mail: data.mail,
                    phone: data.telephoneNumber,
                    room: data.brRoom
                };
            };

            console.log("Logged in as:",context.user);

            const result = rens(context.user, args.uid).then(res => {
                const output = res.map(entry => refactorer(entry));
                return output;
            });

            return result;
        }
    },

    Mutation: {
        asAdmin: (obj, args, context) => {

        },
        createGroup: (obj, args, context) => {
            return createGroupIfLegal(context.user, args);
        }
    }
};

const schema = makeExecutableSchema({
    typeDefs: [typeDefs, Group, User],
    resolvers
});

export default schema;