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

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

        addLinker(linker) {
            this.linkers.push(linker);
        },

        async initEngine() {
            const accountConfig = await Self.findOne({
                fields: ['homedir', 'shell', 'idBase']
            });
            const mailConfig = await models.MailConfig.findOne({
                fields: ['domain']
            });

            const linkers = [];

            for (const Linker of Self.linkers) {
                const linker = await Linker.getLinker();
                if (!linker) continue;
                Object.assign(linker, {accountConfig});
                await linker.init();
                linkers.push(linker);
            }

            Object.assign(accountConfig, {
                linkers,
                domain: mailConfig.domain
            });

            return {
                accountConfig,
                linkers
            };
        },

        async deinitEngine(engine) {
            for (const linker of engine.linkers)
                await linker.deinit();
        },

        async syncUser(userName, password) {
            const engine = await Self.initEngine();
            try {
                await Self.syncUserBase(engine, userName, password, true);
            } finally {
                await Self.deinitEngine(engine);
            }
        },

        async syncUsers() {
            const engine = await Self.initEngine();

            let usersToSync = new Set();
            for (const linker of engine.linkers)
                await linker.getUsers(usersToSync);

            usersToSync = Array.from(usersToSync.values())
                .sort((a, b) => a.localeCompare(b));

            for (let userName of usersToSync) {
                try {
                    // eslint-disable-next-line no-console
                    console.log(`Synchronizing user '${userName}'`);

                    await Self.syncUserBase(engine, userName);

                    // eslint-disable-next-line no-console
                    console.log(` -> User '${userName}' sinchronized`);
                } catch (err) {
                    // eslint-disable-next-line no-console
                    console.error(` -> User '${userName}' synchronization error:`, err.message);
                }
            }

            await Self.deinitEngine(engine);
            await Self.syncRoles();
        },

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

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

            const 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']
                    }
                ]
            });

            const info = {
                user,
                hasAccount: false
            };

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

            const errs = [];

            for (const linker of engine.linkers) {
                try {
                    await linker.syncUser(userName, info, password);
                    if (syncGroups)
                        await linker.syncUserGroups(userName, info);
                } catch (err) {
                    errs.push(err);
                }
            }

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

        async syncRoles() {
            const engine = await Self.initEngine();
            try {
                await Self.rawSql(`CALL account.role_sync`);

                for (const linker of engine.linkers)
                    await linker.syncRoles();
            } finally {
                await Self.deinitEngine(engine);
            }
        }
    });
};