module.exports = Self => {
    Self.getLinker = async function() {
        let NODE_ENV = process.env.NODE_ENV;
        if (!NODE_ENV || NODE_ENV == 'development')
            return null;

        return await Self.findOne({
            fields: ['id', 'rolePrefix', 'userPrefix', 'userHost']
        });
    };

    Object.assign(Self.prototype, {
        async init() {
            const [row] = await Self.rawSql('SELECT VERSION() AS `version`');
            if (row.version.includes('MariaDB'))
                this.dbType = 'MariaDB';
            else
                this.dbType = 'MySQL';
        },

        async syncUser(userName, info, password) {
            let mysqlUser = userName;
            if (this.dbType == 'MySQL')
                mysqlUser = this.userPrefix + mysqlUser;

            const [row] = await Self.rawSql(
                `SELECT COUNT(*) AS nRows
                    FROM mysql.user
                    WHERE User = ? AND Host = ?`,
                [mysqlUser, this.userHost]
            );
            let userExists = row.nRows > 0;

            let isUpdatable = true;
            if (this.dbType == 'MariaDB') {
                const [row] = await Self.rawSql(
                    `SELECT Priv AS priv
                        FROM mysql.global_priv
                        WHERE User = ? AND Host = ?`,
                    [mysqlUser, this.userHost]
                );
                const priv = row && JSON.parse(row.priv);
                isUpdatable = !row || (priv && priv.autogenerated);
            }

            if (!isUpdatable) {
                // eslint-disable-next-line no-console
                console.warn(`RoleConfig.syncUser(): User '${userName}' cannot be updated, not managed by me`);
                return;
            }

            if (info.hasAccount) {
                if (password) {
                    if (!userExists) {
                        await Self.rawSql('CREATE USER ?@? IDENTIFIED BY ?',
                            [mysqlUser, this.userHost, password]);
                        await Self.rawSql(
                            `UPDATE mysql.global_priv
                                SET Priv = JSON_SET(Priv, '$.autogenerated' , TRUE)
                                WHERE User = ? AND Host = ?`,
                            [mysqlUser, this.userHost]
                        );
                        userExists = true;
                    } else {
                        switch (this.dbType) {
                        case 'MariaDB':
                            await Self.rawSql('ALTER USER ?@? IDENTIFIED BY ?',
                                [mysqlUser, this.userHost, password]);
                            break;
                        default:
                            await Self.rawSql('SET PASSWORD FOR ?@? = PASSWORD(?)',
                                [mysqlUser, this.userHost, password]);
                        }
                    }
                }

                if (userExists && this.dbType == 'MariaDB') {
                    let role = `${this.rolePrefix}${info.user.role().name}`;

                    try {
                        await Self.rawSql('REVOKE ALL, GRANT OPTION FROM ?@?',
                            [mysqlUser, this.userHost]);
                    } catch (err) {
                        if (err.code == 'ER_REVOKE_GRANTS')
                            // eslint-disable-next-line no-console
                            console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`);
                        else
                            throw err;
                    }

                    const [row] = await Self.rawSql(
                        `SELECT COUNT(*) AS nRows
                            FROM mysql.user
                            WHERE User = ? AND Host = ''`,
                        [role]
                    );
                    const roleExists = row.nRows > 0;

                    if (roleExists) {
                        await Self.rawSql('GRANT ? TO ?@?',
                            [role, mysqlUser, this.userHost]);
                        await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
                            [role, mysqlUser, this.userHost]);
                    } else {
                        await Self.rawSql('SET DEFAULT ROLE NONE FOR ?@?',
                            [mysqlUser, this.userHost]);
                    }
                }
            } else if (userExists)
                await Self.rawSql('DROP USER ?@?', [mysqlUser, this.userHost]);
        }
    });
};