/* jslint node: true */
'use strict';

//  ENiGMA½
const { removeClient } = require('./client_connections.js');
const ansiNormal = require('./ansi_term.js').normal;
const { userLogin } = require('./user_login.js');
const messageArea = require('./message_area.js');
const { ErrorReasons } = require('./enig_error.js');
const UserProps = require('./user_property.js');
const { loginFactor2_OTP } = require('./user_2fa_otp.js');

//  deps
const _ = require('lodash');
const iconv = require('iconv-lite');

exports.login = login;
exports.login2FA_OTP = login2FA_OTP;
exports.setClientEncoding = setClientEncoding;
exports.logoff = logoff;
exports.prevMenu = prevMenu;
exports.nextMenu = nextMenu;
exports.prevConf = prevConf;
exports.nextConf = nextConf;
exports.prevArea = prevArea;
exports.nextArea = nextArea;
exports.sendForgotPasswordEmail = sendForgotPasswordEmail;
exports.optimizeDatabases = optimizeDatabases;

const handleAuthFailures = (callingMenu, err, cb) => {
    //  already logged in with this user?
    if (
        ErrorReasons.AlreadyLoggedIn === err.reasonCode &&
        _.has(callingMenu, 'menuConfig.config.tooNodeMenu')
    ) {
        return callingMenu.gotoMenu(callingMenu.menuConfig.config.tooNodeMenu, cb);
    }

    //  banned username results in disconnect
    if (ErrorReasons.NotAllowed === err.reasonCode) {
        return logoff(callingMenu, {}, {}, cb);
    }

    const ReasonsMenus = [
        ErrorReasons.TooMany,
        ErrorReasons.Disabled,
        ErrorReasons.Inactive,
        ErrorReasons.Locked,
    ];
    if (ReasonsMenus.includes(err.reasonCode)) {
        const menu = _.get(callingMenu, [
            'menuConfig',
            'config',
            err.reasonCode.toLowerCase(),
        ]);
        return menu ? callingMenu.gotoMenu(menu, cb) : logoff(callingMenu, {}, {}, cb);
    }

    //  Other error
    return callingMenu.prevMenu(cb);
};

function login(callingMenu, formData, extraArgs, cb) {
    userLogin(
        callingMenu.client,
        formData.value.username,
        formData.value.password,
        err => {
            if (err) {
                return handleAuthFailures(callingMenu, err, cb);
            }

            //  success!
            return callingMenu.nextMenu(cb);
        }
    );
}

function login2FA_OTP(callingMenu, formData, extraArgs, cb) {
    loginFactor2_OTP(callingMenu.client, formData.value.token, err => {
        if (err) {
            return handleAuthFailures(callingMenu, err, cb);
        }

        //  success!
        return callingMenu.nextMenu(cb);
    });
}

function logoff(callingMenu, formData, extraArgs, cb) {
    //
    //  Simple logoff. Note that recording of @ logoff properties/stats
    //  occurs elsewhere!
    //
    const client = callingMenu.client;

    setTimeout(() => {
        //
        //  For giggles...
        //
        client.term.write(
            ansiNormal() +
                '\n' +
                iconv.decode(
                    require('crypto').randomBytes(Math.floor(Math.random() * 65) + 20),
                    client.term.outputEncoding
                ) +
                'NO CARRIER',
            null,
            () => {
                //  after data is written, disconnect & remove the client
                removeClient(client);
                return cb(null);
            }
        );
    }, 500);
}

function prevMenu(callingMenu, formData, extraArgs, cb) {
    //  :TODO: this is a pretty big hack -- need the whole key map concep there like other places
    if (formData.key && 'return' === formData.key.name) {
        callingMenu.submitFormData = formData;
    }

    callingMenu.prevMenu(err => {
        if (err) {
            callingMenu.client.log.error(
                { error: err.message },
                'Error attempting to fallback!'
            );
        }
        return cb(err);
    });
}

function nextMenu(callingMenu, formData, extraArgs, cb) {
    callingMenu.nextMenu(err => {
        if (err) {
            callingMenu.client.log.error(
                { error: err.message },
                'Error attempting to go to next menu!'
            );
        }
        return cb(err);
    });
}

//  :TODO: need redrawMenu() and MenuModule.redraw()
function reloadMenu(menu, cb) {
    return menu.reload(cb);
}

function prevConf(callingMenu, formData, extraArgs, cb) {
    const confs = messageArea.getSortedAvailMessageConferences(callingMenu.client);
    const currIndex =
        confs.findIndex(
            e =>
                e.confTag === callingMenu.client.user.properties[UserProps.MessageConfTag]
        ) || confs.length;

    messageArea.changeMessageConference(
        callingMenu.client,
        confs[currIndex - 1].confTag,
        err => {
            if (err) {
                return cb(err); //  logged within changeMessageConference()
            }

            return reloadMenu(callingMenu, cb);
        }
    );
}

function nextConf(callingMenu, formData, extraArgs, cb) {
    const confs = messageArea.getSortedAvailMessageConferences(callingMenu.client);
    let currIndex = confs.findIndex(
        e => e.confTag === callingMenu.client.user.properties[UserProps.MessageConfTag]
    );

    if (currIndex === confs.length - 1) {
        currIndex = -1;
    }

    messageArea.changeMessageConference(
        callingMenu.client,
        confs[currIndex + 1].confTag,
        err => {
            if (err) {
                return cb(err); //  logged within changeMessageConference()
            }

            return reloadMenu(callingMenu, cb);
        }
    );
}

function prevArea(callingMenu, formData, extraArgs, cb) {
    const areas = messageArea.getSortedAvailMessageAreasByConfTag(
        callingMenu.client.user.properties[UserProps.MessageConfTag]
    );
    const currIndex =
        areas.findIndex(
            e =>
                e.areaTag === callingMenu.client.user.properties[UserProps.MessageAreaTag]
        ) || areas.length;

    messageArea.changeMessageArea(
        callingMenu.client,
        areas[currIndex - 1].areaTag,
        err => {
            if (err) {
                return cb(err); //  logged within changeMessageArea()
            }

            return reloadMenu(callingMenu, cb);
        }
    );
}

function nextArea(callingMenu, formData, extraArgs, cb) {
    const areas = messageArea.getSortedAvailMessageAreasByConfTag(
        callingMenu.client.user.properties[UserProps.MessageConfTag]
    );
    let currIndex = areas.findIndex(
        e => e.areaTag === callingMenu.client.user.properties[UserProps.MessageAreaTag]
    );

    if (currIndex === areas.length - 1) {
        currIndex = -1;
    }

    messageArea.changeMessageArea(
        callingMenu.client,
        areas[currIndex + 1].areaTag,
        err => {
            if (err) {
                return cb(err); //  logged within changeMessageArea()
            }

            return reloadMenu(callingMenu, cb);
        }
    );
}

function setClientEncoding(callingMenu, formData, extraArgs, cb) {
    // TODO: Also add other encoding types
    const client = callingMenu.client;
    let encoding = formData.value.encoding;

    client.term.outputEncoding = encoding;

    return callingMenu.nextMenu(cb);
}

function sendForgotPasswordEmail(callingMenu, formData, extraArgs, cb) {
    const username = formData.value.username || callingMenu.client.user.username;

    const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;

    WebPasswordReset.sendForgotPasswordEmail(username, err => {
        if (err) {
            callingMenu.client.log.warn(
                { err: err.message },
                'Failed sending forgot password email'
            );
        }

        if (extraArgs.next) {
            return callingMenu.gotoMenu(extraArgs.next, cb);
        }

        return logoff(callingMenu, formData, extraArgs, cb);
    });
}

function optimizeDatabases(callingMenu, formData, extraArgs, cb) {
    const dbs = require('./database').dbs;
    const client = callingMenu.client;

    client.term.write('\r\n\r\n');

    Object.keys(dbs).forEach(dbName => {
        client.log.info({ dbName }, 'Optimizing database');

        client.term.write(`Optimizing ${dbName}. Please wait...\r\n`);

        //  https://www.sqlite.org/pragma.html#pragma_optimize
        dbs[dbName].run('PRAGMA optimize;', err => {
            if (err) {
                client.log.error(
                    { error: err, dbName },
                    'Error attempting to optimize database'
                );
            }
        });
    });

    return callingMenu.prevMenu(cb);
}
