const models = require('vn-loopback/server/server').models;

module.exports = Self => {
    Object.assign(Self, {
        synchronizers: [],

        addSynchronizer(synchronizer) {
            this.synchronizers.push(synchronizer);
        },

        async getInstance() {
            let instance = await Self.findOne({
                fields: ['homedir', 'shell', 'idBase']
            });
            await instance.synchronizerInit();
            return instance;
        },

        async syncUsers() {
            let instance = await Self.getInstance();

            let usersToSync = await instance.synchronizerGetUsers();
            usersToSync = Array.from(usersToSync.values())
                .sort((a, b) => a.localeCompare(b));

            for (let userName of usersToSync) {
                try {
                    console.log(`Synchronizing user '${userName}'`);
                    await instance.synchronizerSyncUser(userName);
                    console.log(` -> User '${userName}' sinchronized`);
                } catch (err) {
                    console.error(` -> User '${userName}' synchronization error:`, err.message);
                }
            }

            await instance.synchronizerDeinit();
            await Self.syncRoles();
        },

        async syncUser(userName, password) {
            let instance = await Self.getInstance();
            try {
                await instance.synchronizerSyncUser(userName, password, true);
            } finally {
                await instance.synchronizerDeinit();
            }
        },

        async syncRoles() {
            let instance = await Self.getInstance();
            try {
                await instance.synchronizerSyncRoles();
            } finally {
                await instance.synchronizerDeinit();
            }
        },

        async getSynchronizer() {
            return await Self.findOne();
        }
    });

    Object.assign(Self.prototype, {
        async synchronizerInit() {
            let mailConfig = await models.MailConfig.findOne({
                fields: ['domain']
            });

            let synchronizers = [];

            for (let Synchronizer of Self.synchronizers) {
                let synchronizer = await Synchronizer.getSynchronizer();
                if (!synchronizer) continue;
                Object.assign(synchronizer, {
                    accountConfig: this
                });
                await synchronizer.init();
                synchronizers.push(synchronizer);
            }

            Object.assign(this, {
                synchronizers,
                domain: mailConfig.domain
            });
        },

        async synchronizerDeinit() {
            for (let synchronizer of this.synchronizers)
                await synchronizer.deinit();
        },

        async synchronizerSyncUser(userName, password, syncGroups) {
            if (!userName) return;
            userName = userName.toLowerCase();

            // Skip conflicting users
            if (['administrator', 'root'].indexOf(userName) >= 0)
                return;

            let user = await models.VnUser.findOne({
                where: {name: userName},
                fields: [
                    'id',
                    'nickname',
                    'email',
                    'lang',
                    'roleFk',
                    'sync',
                    'active',
                    'created',
                    'password',
                    'updated'
                ],
                include: [
                    {
                        relation: 'roles',
                        scope: {
                            include: {
                                relation: 'inherits',
                                scope: {
                                    fields: ['name']
                                }
                            }
                        }
                    }, {
                        relation: 'role',
                        fields: ['name']
                    }
                ]
            });

            let info = {
                user,
                hasAccount: false
            };

            if (user) {
                let exists = await models.Account.exists(user.id);
                Object.assign(info, {
                    hasAccount: user.active && exists,
                    corporateMail: `${userName}@${this.domain}`,
                    uidNumber: this.idBase + user.id
                });
            }

            let errs = [];

            for (let synchronizer of this.synchronizers) {
                try {
                    await synchronizer.syncUser(userName, info, password);
                    if (syncGroups)
                        await synchronizer.syncUserGroups(userName, info);
                } catch (err) {
                    errs.push(err);
                }
            }

            if (errs.length) throw errs[0];
        },

        async synchronizerGetUsers() {
            let usersToSync = new Set();

            for (let synchronizer of this.synchronizers)
                await synchronizer.getUsers(usersToSync);

            return usersToSync;
        },

        async synchronizerSyncRoles() {
            for (let synchronizer of this.synchronizers)
                await synchronizer.syncRoles();
        }
    });
};