var ChannelModule = require("./module");
var Flags = require("../flags");
var Account = require("../account");
var db = require("../database");
import Promise from 'bluebird';

const dbSetChannelRank = Promise.promisify(db.channels.setRank);

const TYPE_SET_CHANNEL_RANK = {
    name: "string",
    rank: "number"
};

function RankModule(_channel) {
    ChannelModule.apply(this, arguments);

    if (this.channel.modules.chat) {
        this.channel.modules.chat.registerCommand("/rank", this.handleCmdRank.bind(this));
    }
}

RankModule.prototype = Object.create(ChannelModule.prototype);

RankModule.prototype.onUserPostJoin = function (user) {
    user.socket.typecheckedOn("setChannelRank", TYPE_SET_CHANNEL_RANK, this.handleRankChange.bind(this, user));
    var self = this;
    user.socket.on("requestChannelRanks", function () {
        self.sendChannelRanks([user]);
    });
};

RankModule.prototype.sendChannelRanks = function (users) {
    if (!this.channel.is(Flags.C_REGISTERED)) {
        return;
    }

    db.channels.allRanks(this.channel.name, function (err, ranks) {
        if (err) {
            return;
        }

        users.forEach(function (u) {
            if (u.account.effectiveRank >= 3) {
                u.socket.emit("channelRanks", ranks);
            }
        });
    });
};

RankModule.prototype.handleCmdRank = function (user, msg, _meta) {
    var args = msg.split(" ");
    args.shift(); /* shift off /rank */
    var name = args.shift();
    var rank = parseInt(args.shift());

    if (!name || isNaN(rank)) {
        user.socket.emit("noflood", {
            action: "/rank",
            msg: "Syntax: /rank <username> <rank>.  <rank> must be a positive integer > 1"
        });
        return;
    }

    this.handleRankChange(user, { name: name, rank: rank });
};

RankModule.prototype.handleRankChange = function (user, data) {
    if (user.account.effectiveRank < 3) {
        return;
    }

    var rank = data.rank;
    var userrank = user.account.effectiveRank;
    var name = data.name.substring(0, 20).toLowerCase();

    if (!name.match(/^[a-zA-Z0-9_-]{1,20}$/)) {
        user.socket.emit("channelRankFail", {
            msg: "Invalid target name " + data.name
        });
        return;
    }

    if (isNaN(rank) || rank < 1 || (rank >= userrank && !(userrank === 4 && rank === 4))) {
        user.socket.emit("channelRankFail", {
            msg: "Updating user rank failed: You can't promote someone to a rank equal " +
                 "or higher than yourself, or demote them to below rank 1."
        });
        return;
    }

    var receiver;
    var lowerName = name.toLowerCase();
    for (var i = 0; i < this.channel.users.length; i++) {
        if (this.channel.users[i].getLowerName() === lowerName) {
            receiver = this.channel.users[i];
            break;
        }
    }

    if (name === user.getLowerName()) {
        user.socket.emit("channelRankFail", {
            msg: "Updating user rank failed: You can't promote or demote yourself."
        });
        return;
    }

    if (!this.channel.is(Flags.C_REGISTERED)) {
        user.socket.emit("channelRankFail", {
            msg: "Updating user rank failed: in an unregistered channel, a user must " +
                 "be online in the channel in order to have their rank changed."
        });
        return;
    }

    if (receiver) {
        var current = Math.max(receiver.account.globalRank, receiver.account.channelRank);
        if (current >= userrank && !(userrank === 4 && current === 4)) {
            user.socket.emit("channelRankFail", {
                msg: "Updating user rank failed: You can't promote or demote "+
                     "someone who has equal or higher rank than yourself"
            });
            return;
        }

        const oldRank = receiver.account.effectiveRank;
        receiver.account.channelRank = rank;
        receiver.account.effectiveRank = Math.max(receiver.account.globalRank, rank);
        receiver.emit("effectiveRankChange", receiver.account.effectiveRank, oldRank);
        receiver.socket.emit("rank", receiver.account.effectiveRank);
        this.channel.logger.log("[mod] " + user.getName() + " set " + name + "'s rank " +
                                "to " + rank);
        this.channel.broadcastAll("setUserRank", data);

        if (!this.channel.is(Flags.C_REGISTERED)) {
            user.socket.emit("channelRankFail", {
                msg: "This channel is not registered.  Any rank changes are temporary " +
                     "and not stored in the database."
            });
            return;
        }

        if (!receiver.is(Flags.U_REGISTERED)) {
            user.socket.emit("channelRankFail", {
                msg: "The user you promoted is not a registered account.  " +
                     "Any rank changes are temporary and not stored in the database."
            });
            return;
        }

        data.userrank = userrank;

        this.updateDatabase(data, function (err) {
            if (err) {
                user.socket.emit("channelRankFail", {
                    msg: "Database failure when updating rank"
                });
            }
        });
    } else {
        data.userrank = userrank;
        var self = this;
        this.updateDatabase(data, function (err) {
            if (err) {
                user.socket.emit("channelRankFail", {
                    msg: "Updating user rank failed: " + err
                });
                return;
            }
            self.channel.logger.log("[mod] " + user.getName() + " set " + data.name +
                                    "'s rank to " + rank);
            self.channel.broadcastAll("setUserRank", data);
            if (self.channel.modules.chat) {
                self.channel.modules.chat.sendModMessage(
                    user.getName() + " set " + data.name + "'s rank to " + rank,
                    3
                );
            }
        });
    }
};

RankModule.prototype.updateDatabase = function (data, cb) {
    var chan = this.channel;
    Account.rankForName(data.name, this.channel.name).then(rank => {
        if (rank >= data.userrank && !(rank === 4 && data.userrank === 4)) {
            throw new Error(
                    "You can't promote or demote someone" +
                    " with equal or higher rank than you."
            );
        }

        return dbSetChannelRank(chan.name, data.name, data.rank);
    }).then(() => {
        process.nextTick(cb);
    }).catch(error => {
        process.nextTick(cb, error.message || error);
    });
};

module.exports = RankModule;
