/**
 * @file Resolvers des groupes
 * @author ofacklam
 */

import {User} from './users';
import {Announcement, Event, PrivatePost, Question, Answer} from './messages';
import {Group as LDAP_Group} from '../../ldap/export/group';
import knex from '../../../db/knex_router';
import { Context } from '../typeDefs/queries';
import { ApolloError, AuthenticationError } from 'apollo-server-core';
import { Request, UserJoinGroup, GroupJoinMetagroup, GroupCoauthorEvent } from './requests';
import { GroupSet } from '../models/tools';

export abstract class Group {

    /**
     * @abstract
     * @memberof GraphQL
     * @class Group
     * @summary Resolvers des groupes
     * @classdesc Une classe abstraite représentant l'interface group du schéma.
     * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement.
     * @arg {string} gid - Identifiant du groupe, supposé valide
     * @rights connectedOrOnplatal
     */
    constructor(gid: string) {
        this.gid = gid;
        this.m_dataLoaded = false;
    }

    /**
     * @memberof GraphQL.Group#
     * @function fetchData
     * @summary Fonction qui va chercher toutes les données sur ce groupe dans le stockage approprié, si ce n'est pas déja fait.
     * @returns {Promise(boolean)} Renvoie true si le chargement est réussi.
     * @async
     * @protected
     * @abstract
     */
    protected abstract async fetchData(): Promise<boolean>

    /**
     * Protected properties.
     * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la base (que ce soit pour SimpleGroup ou MetaGroup),
     * ce qui permet d'etre plus efficace.
     * La variable dataLoaded témoigne de si on est déja allés chercher les données.
     * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma.
     */
    protected m_dataLoaded: boolean
    protected m_createdAt: string
    protected m_updatedAt: string
    protected m_name: string
    protected m_description: string
    protected m_mail: string
    protected m_website: string
    protected m_frontPage: string
    protected m_postsSummary: string 

    /**
     * Ci-dessous les resolvers a proprement parler.
     */

    __typename : string = "Bad group type";

    /** @rights connectedOrOnplatal */
    gid: string;

    /**
     * @memberof GraphQL.Group#
     * @function createdAt
     * @summary Renvoie la date de création
     * @return {Promise(string)}
     * @rights connectedOrOnplatal
     * @async
     */
    async createdAt(args, context: Context, info): Promise<string> {
        await this.fetchData();
        return this.m_createdAt;
    }
    
    /**
     * @memberof GraphQL.Group#
     * @function updatedAt
     * @summary Renvoie la date de mise a jour
     * @return {Promise(string)}
     * @rights connectedOrOnplatal
     * @async
     */
    async updatedAt(args, context: Context, info): Promise<string> {
        await this.fetchData();
        return this.m_updatedAt;
    }

    /**
     * @memberof GraphQL.Group#
     * @function name
     * @summary Renvoie le nom du groupe
     * @return {Promise(string)}
     * @rights connectedOrOnplatal
     * @async
     */
    async name(args, context: Context, info): Promise<string> {
        await this.fetchData();
        return this.m_name;
    }

    /**
     * @memberof GraphQL.Group#
     * @function description
     * @summary Renvoie la description du groupe
     * @return {Promise(string)}
     * @rights viewer
     * @async
     */
    async description(args, context: Context, info): Promise<string> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_description;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function mail
     * @summary Renvoie l'adresse mail
     * @return {Promise(string)}
     * @rights viewer
     * @async
     */
    async mail(args, context: Context, info): Promise<string> {
        if (context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_mail;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function website
     * @summary Renvoie l'adresse du site web
     * @return {Promise(string)}
     * @rights viewer
     * @async
     */
    async website(args, context: Context, info): Promise<string> {
        if (context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_website;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function admins
     * @summary Renvoie la liste des admins
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     * @abstract
     */
    abstract async admins(args, context: Context, info): Promise<User[]>

    /**
     * @memberof GraphQL.Group#
     * @function frontPage
     * @summary Renvoie la page principale
     * @return {Promise(string)}
     * @rights viewer
     * @async
     */
    async frontPage(args, context: Context, info): Promise<string> {
        if (context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_frontPage;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function questions
     * @summary Renvoie les questions adressées a ce groupe
     * @return {Promise(Question[])}
     * @rights viewer
     * @async
     */
    async questions(args, context: Context, info): Promise<Question[]> {
        if (context.models.auth.isViewer(this.gid)) {
            throw "Not implemented"

            // let result = await knex('questions').select().whereIn('id', received_messages);
            // for(let entry of result){
            //     entry.type = "Question";
            // }
            // return result;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function answers
     * @summary Renvoie les réponses de ce groupe
     * @return {Promise(Answer[])}
     * @rights viewer
     * @async
     */
    async answers(args, context: Context, info): Promise<Answer[]> {
        if (context.models.auth.isViewer(this.gid)) {
            throw "Not implemented"

            // let received_messages = await selectors.recievedMessages(user, groupUID);
            // let result = await knex('answers').select().whereIn('id', received_messages);
            // for(let entry of result){
            //     entry.type = "Answer";
            // }
            // return result;
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.Group#
     * @function announcementsFromGroup
     * @summary Renvoie les annonces créées par ce groupe
     * @return {Promise(Announcement[])}
     * @rights member
     * @async
     */
    async announcementsFromGroup(args, context: Context, info): Promise<Announcement[]> {
        if(context.models.auth.isMember(this.gid)) {
            //03/03/19
            let res = new GroupSet();
            res.add(this.gid);
            return context.models.message.getAllAnnouncementsSent(res);
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function announcementsToGroup
     * @summary Renvoie les annonces adressées a ce groupe
     * @return {Promise(Announcement[])}
     * @rights member
     * @async
     */
    async announcementsToGroup(args, context: Context, info): Promise<Announcement[]> {
        if(context.models.auth.isMember(this.gid)) {
            //03/03/19
            let res = new GroupSet();
            res.add(this.gid);
            return context.models.message.getAllAnnouncementsReceived(res);
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function eventsFromGroup
     * @summary Renvoie les evenements créés par ce groupe
     * @return {Promise(Event[])}
     * @rights member
     * @async
     */
    async eventsFromGroup(args, context: Context, info): Promise<Event[]> {
        if (context.models.auth.isMember(this.gid)) {
            //03/03/19
            let res = new GroupSet();
            res.add(this.gid);
            return context.models.message.getAllEventsFrom(res);
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function eventsToGroup
     * @summary Renvoie les evenements adressées a ce groupe
     * @return {Promise(Event[])}
     * @rights member
     * @async
     */
    async eventsToGroup(args, context: Context, info): Promise<Event[]> {
        if (context.models.auth.isMember(this.gid)) {
            //03/03/19
            let res = new GroupSet();
            res.add(this.gid);
            return context.models.message.getAllEventsTo(res);
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function privatePosts
     * @summary Renvoie les posts internes du groupe
     * @return {Promise(PrivatePost[])}
     * @rights member
     * @async
     */
    async privatePosts(args, context: Context, info): Promise<PrivatePost[]> {
        if (context.models.auth.isMember(this.gid)) {
            throw "Not implemented"
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function postsSummary
     * @summary Renvoie le résumé interne
     * @return {Promise(string)}
     * @rights member
     * @async
     */
    async postsSummary(args, context: Context, info): Promise<string> {
        if (context.models.auth.isMember(this.gid)) {
            await this.fetchData();
            return this.m_postsSummary;
        }
        throw new AuthenticationError("Not a member");
    }

    /**
     * @memberof GraphQL.Group#
     * @function requestsToGroup
     * @summary Renvoie toutes les requetes adressées au groupe.
     * @return {Promise(Request[])}
     * @rights admin
     * @async
     */
    async requestsToGroup(args, context: Context, info): Promise<Request[]> {
        if (context.models.auth.isAdmin(this.gid)) {
            return context.models.request.getRequestsToGroup(this.gid);
        }
        throw new AuthenticationError("Not an admin");
    }

    /**
     * @memberof GraphQL.Group#
     * @function userJoinGroupRequestsToGroup
     * @summary Renvoie toutes les requetes UserJoinGroup adressées au groupe.
     * @return {Promise(UserJoinGroup[])}
     * @rights admin
     * @async
     */
    async userJoinGroupRequestsToGroup(args, context: Context, info): Promise<UserJoinGroup[]> {
        if (context.models.auth.isAdmin(this.gid)) {
            return context.models.request.getUserJoinGroupRequestsToGroup(this.gid);
        }
        throw new AuthenticationError("Not an admin");
    }

    /**
     * @memberof GraphQL.Group#
     * @function groupJoinMetagroupRequestsToGroup
     * @summary Renvoie toutes les requetes GroupJoinMetagroup adressées au groupe.
     * @return {Promise(GroupJoinMetagroup[])}
     * @rights admin
     * @async
     */
    async groupJoinMetagroupRequestsToGroup(args, context: Context, info): Promise<GroupJoinMetagroup[]> {
        if (context.models.auth.isAdmin(this.gid)) {
            return context.models.request.getGroupJoinMetagroupRequestsToGroup(this.gid);
        }
        throw new AuthenticationError("Not an admin");
    }

    /**
     * @memberof GraphQL.Group#
     * @function groupCoauthorEventRequestsToGroup
     * @summary Renvoie toutes les requetes GroupCoauthorEvent adressées au groupe.
     * @return {Promise(GroupCoauthorEvent[])}
     * @rights admin
     * @async
     */
    async groupCoauthorEventRequestsToGroup(args, context: Context, info): Promise<GroupCoauthorEvent[]> {
        if (context.models.auth.isAdmin(this.gid)) {
            return context.models.request.getGroupCoauthorEventRequestsToGroup(this.gid);
        }
        throw new AuthenticationError("Not an admin");
    }

    /**
     * @memberof GraphQL.Group#
     * @function visibilityEdges
     * @summary Renvoie les groupes auxquels ce groupe est visible.
     * @return {Promise(Group[])}
     * @rights viewer
     * @async
     */
    async visibilityEdges(args, context: Context, info): Promise<Group[]> {
        if (context.models.auth.isViewer(this.gid)) {
            throw "Not implemented"
        }
        throw new AuthenticationError("Not a viewer");
    }
}


export class SimpleGroup extends Group {

    /**
     * @memberof GraphQL
     * @class SimpleGroup
     * @extends GraphQL.Group
     * @summary Resolvers des groupes simples
     * @classdesc Une classe représentant le type SimpleGroup du schéma.
     * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement.
     * @arg {string} gid - Identifiant du groupe simple, supposé valide.
     * @rights connectedOrOnplatal
     */
    constructor(gid: string) {
        super(gid);
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function tryCreate
     * @summary Fonction qui va essayer de créer le groupe simple correspondant
     * @arg {string} gid - Identifiant du groupe simple, sans hypothese sur la validité.
     * @returns {Promise(SimpleGroup)} - Renvoie le groupe simple créé, ou null en cas d'erreur.
     * @rights connectedOrOnplatal
     * @async
     * @static
     */
    static async tryCreate(gid: string): Promise<SimpleGroup> {
        let g = new SimpleGroup(gid);
        if(await g.fetchData()) {
            return g;
        }
        return null;
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function fetchData
     * @summary Fonction qui va chercher toutes les données sur ce groupe simple dans le LDAP, si ce n'est pas déja fait.
     * @returns {Promise(boolean)} Renvoie true si le chargement est réussi.
     * @async
     * @protected
     */
    protected async fetchData(): Promise<boolean> {
        if(!this.m_dataLoaded) {
            try {
                let data = await LDAP_Group.peek(this.gid);
                if(data.gid !== this.gid) {
                    throw "Error in search";
                }

                //this.m_createdAt = data.createdAt;
                //this.m_updatedAt = data.updatedAt;
                this.m_name = data.name;
                this.m_description = data.description;
                //this.m_mail = data.mail;
                //this.m_website = data.website;

                this.m_members = data.members;
                //this.m_speakers = data.speakers;
                this.m_admins = data.admins;
                //this.m_likers = data.likers;

                //this.m_frontPage = data.frontPage;
                //this.m_postsSummary = data.postsSummary;

                //this.m_parent = data.parent;
                //this.m_children = data.children;
                //this.m_school = ???;

                this.m_dataLoaded = true;
                return true;
            }
            catch {
                return false;
            }
        }
        return true;
    }

    /**
     * Protected properties.
     * Ce sont tous les champs triviaux, ie qui peuvent etre récupérés en une seule requete a la base (que ce soit pour SimpleGroup ou MetaGroup),
     * ce qui permet d'etre plus efficace.
     * La variable dataLoaded témoigne de si on est déja allés chercher les données.
     * ATTENTION. Ce ne sont PAS directement les resolvers, il FAUT donc qu'ils aient un nom DIFFÉRENT du nom des champs dans le schéma.
     */
    protected m_members: string[]
    protected m_speakers: string[]
    protected m_admins: string[]
    protected m_likers: string[]
    protected m_parent: string
    protected m_children: string[]
    protected m_school: string

    /**
     * Ci-dessous les resolvers a proprement parler.
     */

    __typename: string = "SimpleGroup";

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function members
     * @summary Renvoie la liste des membres
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     */
    async members(args, context: Context, info): Promise<User[]> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_members.map(uid => {
                return new User(uid);
            });
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function speakers
     * @summary Renvoie la liste des speakers
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     */
    async speakers(args, context: Context, info): Promise<User[]> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_speakers.map(uid => {
                return new User(uid);
            });
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function admins
     * @summary Renvoie la liste des admins
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     */
    async admins(args, context: Context, info): Promise<User[]> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_admins.map(uid => {
                return new User(uid);
            });
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function likers
     * @summary Renvoie la liste des sympathisants
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     */
    async likers(args, context: Context, info): Promise<User[]> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_likers.map(uid => {
                return new User(uid);
            });
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function parent
     * @summary Renvoie le groupe parent
     * @return {Promise(SimpleGroup)}
     * @rights viewer
     * @async
     */
    async parent(args, context: Context, info): Promise<SimpleGroup> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            if(this.m_parent == 'root') {
                return null;
            }
            else {
                return new SimpleGroup(this.m_parent);
            }
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function children
     * @summary Renvoie la liste des groupes enfants
     * @return {Promise(SimpleGroup[])}
     * @rights viewer
     * @async
     */
    async children(args, context: Context, info): Promise<SimpleGroup[]> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_children.map(gid => {
                return new SimpleGroup(gid);
            });
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function memberOfMeta
     * @summary Renvoie la liste des méta-groupes dont ce groupe fait partie
     * @return {Promise(MetaGroup[])}
     * @rights viewer
     * @async
     */
    async memberOfMeta(args, context: Context, info): Promise<MetaGroup[]> {
        if(context.models.auth.isViewer(this.gid)) {
            throw "Not implemented";
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.SimpleGroup#
     * @function school
     * @summary Renvoie l'école d'origine
     * @return {Promise(string)}
     * @rights viewer
     * @async
     */
    async school(args, context: Context, info): Promise<string> {
        if(context.models.auth.isViewer(this.gid)) {
            await this.fetchData();
            return this.m_school;
        }
        throw new AuthenticationError("Not a viewer");
    }
}


export class MetaGroup extends Group {

    /**
     * @memberof GraphQL
     * @class MetaGroup
     * @extends GraphQL.Group
     * @summary Resolvers des méta-groupes
     * @classdesc Une classe représentant le type MetaGroup du schéma.
     * Comme Apollo Server, par défaut, appelle la propriété / fonction avec le nom a résoudre, il n'y a pas besoin d'écrire les resolvers explicitement.
     * @arg {string} gid - Identifiant du méta-groupe, supposé valide
     * @rights connectedOrOnplatal
     */
    constructor(gid: string) {
        super(gid);
    }

    /**
     * @memberof GraphQL.MetaGroup#
     * @function tryCreate
     * @summary Fonction qui va essayer de créer le méta-groupe correspondant
     * @arg {string} gid - Identifiant du méta-groupe, sans hypothese sur la validité.
     * @returns {Promise(MetaGroup)} - Renvoie le méta-groupe créé, ou null en cas d'erreur.
     * @rights connectedOrOnplatal
     * @async
     * @static
     */
    static async tryCreate(gid: string): Promise<MetaGroup> {
        let g = new MetaGroup(gid);
        if(await g.fetchData()) {
            return g;
        }
        return null;
    }

    /**
     * @memberof GraphQL.MetaGroup#
     * @function fetchData
     * @summary Fonction qui va chercher toutes les données sur ce méta-groupe dans la BDD, si ce n'est pas déja fait.
     * @returns {Promise(boolean)} Renvoie true si le chargement est réussi.
     * @async
     * @protected
     */
    protected async fetchData(): Promise<boolean> {
        if (!this.m_dataLoaded) {
            let data = await knex.select('created_at',
                'updated_at', 
                'name', 
                'description',
                //'mail', 
                //'posts_summary', 
                //'front_page', 
                'website').from('groups').where('gid', this.gid);

            if(data.length > 0) {
                let g = data[0];

                this.m_createdAt = g.created_at;
                this.m_updatedAt = g.updated_at;
                this.m_name = g.name;
                this.m_description = g.description;
                //this.m_mail = g.mail;
                this.m_website = g.website;

                //this.m_frontPage = g.frontPage;
                //this.m_postsSummary = g.postsSummary;

                this.m_dataLoaded = true;
                return true;
            }
            else {
                return false;
            }
        }
        return true;
    }

    /**
     * Ci-dessous les resolvers a proprement parler.
     */

    __typename: string = "MetaGroup"; 

    /**
     * @memberof GraphQL.MetaGroup#
     * @function admins
     * @summary Renvoie la liste des admins
     * @return {Promise(User[])}
     * @rights viewer
     * @async
     */
    async admins(args, context: Context, info): Promise<User[]> {
        if (context.models.auth.isViewer(this.gid)) {
            throw "Not implemented";
        }
        throw new AuthenticationError("Not a viewer");
    }

    /**
     * @memberof GraphQL.MetaGroup#
     * @function members
     * @summary Renvoie la liste des groupes membres
     * @return {Promise(SimpleGroup[])}
     * @rights viewer
     * @async
     */
    async members(args, context: Context, info): Promise<SimpleGroup[]> {
        if (context.models.auth.isViewer(this.gid)) {
            throw "Not implemented"

            /*let member_group_list = await knex.distinct().select().from('groups')
                .innerJoin('meta_group_membership', 'groups.uid', 'meta_group_membership.member_uid')
                .where('meta_group_membership.union_uid', '=', group.gid);
            let members = new Set;
            for (const memberGroup of member_group_list) {
                let res = await getGroupMemberUsers(quser, new Group(memberGroup));
                for (const member of res.values()) {
                    members.add(member);
                }
            }
            return members;*/
        }
        throw new AuthenticationError("Not a viewer");
    }

}