const fs = require('fs');
const path = require('path');
const moment = require('moment');
const _ = require('lodash'); // TODO
const log = require('loglevel');

const constants = require('../models/constants');
const constantsXml = require('../models/constants-xml');
const enums = require('../models/enums');
const enumsXml = require('../models/enums-xml');
const HelpersCtrl = require('./helpers');

const Employee = require('../models/employee');
const EmployeeServ = require('../services/employee');

const Pos = require('../models/pos');
const PosCtrl = require('./pos');
const PosTransactionCtrl = require('./pos-transaction');
const FinancialTransactionCtrl = require('./financial-transaction');
const FinancialTransaction = require('../models/financial-transaction');
const FareEnvironmentConfiguration = require('../models/fare-environment-configuration');

class EmployeeCtrl {
    constructor () {
        this.model = Employee.Employee;
        this.modelRole = Employee.EmployeeRole;
        this.modelWorkTime = Employee.EmployeeWorkTime;
        this.service = new EmployeeServ();

        this.posModel = Pos;
        this.posCtrl = new PosCtrl();
        this.posTransactionCtrl = new PosTransactionCtrl();
        this.financialTransactionCtrl = new FinancialTransactionCtrl();
        this.financialTransactionModel = FinancialTransaction.FinancialTransaction;
        this.fareEnvironmentModel = FareEnvironmentConfiguration;
        this.helpersCtrl = new HelpersCtrl();
        this.timeFormat = 'HH:mm';
    }

    login = async (req, res) => {
        try {
            const username = req.body.username;
            const userpass = req.body.password;

            const identifierType = 'ID';
            const credentials = {
                id: username,
                password: userpass
            };

            if (!credentials.id && !credentials.password) {
                return res.status(400).send('Invalid client request');
            }

            let employeeLogin = await this.service.login(identifierType, credentials);

            if (!employeeLogin) {
                return res.status(400).send('No connection to backend system');
            }

            if (employeeLogin.hasOwnProperty('errorMessage') && employeeLogin.errorMessage.includes('invalid password')) {
                let attempts = employeeLogin.errorMessage.match(/\d+/g);
                let remainingAttempts = parseInt(attempts[1]) - parseInt(attempts[0]);

                if (employeeLogin != null && remainingAttempts > 0) {
                    return res.status(400).send('Incorrect Password. Please try again. You have ' + remainingAttempts + ' remaining attempts before your account is locked.');
                } else {
                    return res.status(400).send('Your account has been locked. Please contact the administrator.');
                }
            }

            if (employeeLogin.hasOwnProperty('errorMessage') && employeeLogin.errorMessage.includes('locked')) {
                return res.status(400).send('Your account has been locked. Please contact the administrator.');
            }

            if (employeeLogin.hasOwnProperty('permissions') && employeeLogin.permissions.length == 0) {
                return res.status(400).send('Your account does not have any permissions. Please contact the administrator.');
            }

            const employeeWorkTime = await this.modelWorkTime.create({
                employeeId: employeeLogin.id,
                firstName: employeeLogin.firstName,
                lastName: employeeLogin.lastName,
                employeeRoleId: employeeLogin.employeeRoleId,
                employeeRoleName: employeeLogin.employeeRoleName,
                loginTime: moment.utc().toISOString(),
                logoutTime: moment.utc(0).toISOString(),
                startBalanceAmount: 0,
                endBalanceAmount: 0,
                diffBalanceAmount: 0,
                sessionId: global.currentPosData.transactionCounter
            });

            global.currentEmployeeData = {
                employeeId: employeeWorkTime.employeeId,
                employeeName: `${employeeWorkTime.firstName} ${employeeWorkTime.lastName}`,
                employeeRoleId: employeeWorkTime.employeeRoleId,
                employeeRoleName: employeeWorkTime.employeeRoleName,
                employeeLoginTime: moment.utc(employeeWorkTime.loginTime).toISOString(),
                employeeStartBalanceAmount: employeeWorkTime.startBalanceAmount,
                employeeEndBalanceAmount: employeeWorkTime.endBalanceAmount,
                employeeSessionId: employeeWorkTime.sessionId
            };

            await this.posTransactionCtrl.create({
                function: 'logOnOff',
                component: 'mainCpuBoard',
                parameters: {
                    logOnOff: {
                        desiredState: constantsXml.desiredStateType.logon,
                        logOnOffMethod: constantsXml.logOnOffMethodType.manual,
                        mode: (employeeLogin.EmployeeRoleName == 'Maintenance') ? constantsXml.modeType.maintenanceMode : constantsXml.modeType.revenueMode,
                        finalState: constantsXml.finalStateType.loggedIn
                    }
                }
            });

            res.status(200).json({
                token: new Buffer.from(`${employeeLogin.id}-${employeeLogin.employeeRoleName}-${employeeLogin.employeeRoleId.toString()}`).toString('base64'),
                user: {
                    firstLogin: employeeLogin.firstLogin,
                    name: global.currentEmployeeData.employeeName,
                    roleId: global.currentEmployeeData.employeeRoleId,
                    role: global.currentEmployeeData.employeeRoleName
                }
            });
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getByUsername = async (req, res) => {
        try {
            const username = req.body.username;

            const identifierType = 'ID';

            if (!username) {
                return res.status(400).send('Invalid client request');
            }

            let employee = await this.service.getByUsername(identifierType, username);

            if (employee == null) {
                return res.status(400).send('The username that you have entered is invalid. Please try again.');
            }

            if (employee == 0) {
                return res.status(400).send('No connection to backend system');
            }

            const activeConfig = await this.fareEnvironmentModel.Configuration.findOne({distributionVersion: global.currentPosData.distributionVersion, isActive: true});
            const userRoles = await this.modelRole.find({fareEnvironmentConfigurationId: activeConfig._id, employeeRoleId: employee.employeeRoleId});

            if (!userRoles.length) {
                return res.status(400).send('Your account doesn\'t have permission to access the device');
            }

            if (employee.locked) {
                return res.status(400).send('Your account has been locked. Please contact the administrator.');
            }

            let employeeLogout = await this.modelWorkTime.find({logoutTime: moment.utc(0).toISOString()}).sort({loginTime: -1});
            if (employeeLogout.length) {
                for (let i=0; i<employeeLogout.length; i++) {
                    let employeeLogoutId = employeeLogout[i].employeeId;
                    let employeeLoginTime = employeeLogout[i].loginTime;
                    let employeeLogoutTime = employeeLoginTime;
                    let employeeLogins = await this.modelWorkTime.find({employeeId: employeeLogoutId, loginTime: {$gte: moment.utc(employeeLoginTime).startOf('day').toISOString(), $lt: moment.utc(employeeLoginTime).endOf('day').toISOString()}}).sort({loginTime: -1});
                    if (employeeLogins.length > 1) {
                        let employeeLogin = employeeLogins.findIndex((f) => moment.utc(f.loginTime).toISOString() == moment.utc(employeeLoginTime).toISOString());
                        let employeeLoginIndex = (employeeLogin > 0) ? employeeLogin-1 : employeeLogin;
                        employeeLogoutTime = (moment.utc(employeeLoginTime).diff(moment.utc(employeeLogins[employeeLoginIndex].loginTime), 'milliseconds')) ? moment.utc(employeeLogins[employeeLoginIndex].loginTime).subtract(1, 'milliseconds').toISOString() : moment.utc().toISOString();
                    } else {
                        let employeeTransactions = await this.financialTransactionModel.find({employeeId: employeeLogoutId, transactionTime: {$gte: moment.utc(employeeLoginTime).toISOString(), $lt: moment.utc(employeeLoginTime).endOf('day').toISOString()}}).sort({transactionTime: -1});
                        employeeLogoutTime = (employeeTransactions.length > 1) ? moment.utc(employeeTransactions[0].transactionTime).add(1, 'milliseconds').toISOString() : moment.utc(employeeLogoutTime).add(1, 'milliseconds').toISOString();
                    }

                    await this.modelWorkTime.findOneAndUpdate({employeeId: employeeLogoutId, loginTime: moment.utc(employeeLoginTime).toISOString(), logoutTime: moment.utc(0).toISOString()}, {
                        logoutTime: employeeLogoutTime
                    }).sort({loginTime: -1});
                }
            }

            res.status(200).json(employee);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    logout = async (req, res) => {
        try {
            if (typeof global.currentEmployeeData !== undefined && typeof global.currentEmployeeData === 'object' && Object.keys(global.currentEmployeeData).length === 0 && !global.currentEmployeeData.hasOwnProperty('employeeId')) {
                return true;
            }

            let waybill = null;
            let employeeLogout = await this.modelWorkTime.findOne({employeeId: global.currentEmployeeData.employeeId, logoutTime: moment.utc(0).toISOString()}).sort({loginTime: -1});

            if (!employeeLogout) {
                return res.status(400).send('No connection to backend system');
            }

            if (!/maintenance/i.test(employeeLogout.employeeRoleName)) {
                const employeeTransactions = await this.financialTransactionModel.find({employeeId: employeeLogout.employeeId, $and: [{transactionTime: {$gt: moment.utc(employeeLogout.loginTime).toISOString()}}, {transactionTime: {$lt: moment.utc().toISOString()}}]}).sort({transactionTime: -1});
                if (employeeTransactions) {
                    waybill = await this.financialTransactionCtrl.getWaybill(_.groupBy(employeeTransactions, 'employeeId'), false);
                }
            }

            await this.posTransactionCtrl.create({
                function: 'logOnOff',
                component: 'mainCpuBoard',
                parameters: {
                    logOnOff: {
                        desiredState: constantsXml.desiredStateType.logoff,
                        logOnOffMethod: constantsXml.logOnOffMethodType.manual, // constantsXml.logOnOffMethodType.timerExpired
                        mode: (employeeLogout.employeeRoleName == 'Maintenance') ? constantsXml.modeType.maintenanceMode : constantsXml.modeType.revenueMode,
                        finalState: constantsXml.finalStateType.loggedOff
                    }
                }
            });

            await this.posTransactionCtrl.create({
                function: 'deviceStatus',
                component: 'mainCpuBoard',
                parameters: {}
            });

            const employeeWorkTime = await this.modelWorkTime.findOneAndUpdate({employeeId: employeeLogout.employeeId, logoutTime: moment.utc(0).toISOString()}, {
                logoutTime: moment.utc(global.currentEmployeeData.employeeLogoutTime).toISOString()
            }).sort({loginTime: -1});

            await this.posCtrl.switchConfig();

            global.currentEmployeeData = {};

            res.status(200).json(waybill);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    logoutEndOfWorkHour = async () => {
        try {
            const device = await this.posModel.findOne({});
            process.env.POS_AUTO_LOGOUT_TIME = (device) ? device.endOfWorkHour : '00:00';

            if (typeof global.currentEmployeeData !== undefined && typeof global.currentEmployeeData === 'object' && Object.keys(global.currentEmployeeData).length !== 0 && global.currentEmployeeData.hasOwnProperty('employeeId') && !global.currentEmployeeData.hasOwnProperty('employeeLogoutTime')) {
                global.io.emit('notify frontend', 'logoutEndOfWorkHour');
            }

            return process.env.POS_AUTO_LOGOUT_TIME;
        } catch (err) {
            log.error(err);
        }
    }

    lock = async (req, res) => {
        try {
            const isManually = req.query.manually;
            let employeeLock = global.currentEmployeeData;

            await this.posTransactionCtrl.create({
                function: 'logOnOff',
                component: 'mainCpuBoard',
                parameters: {
                    logOnOff: {
                        desiredState: constantsXml.desiredStateType.lock,
                        logOnOffMethod: (isManually == 1) ? constantsXml.logOnOffMethodType.manual : constantsXml.logOnOffMethodType.standbyMode,
                        mode: (employeeLock.employeeRoleName == 'Maintenance') ? constantsXml.modeType.maintenanceMode : constantsXml.modeType.revenueMode,
                        finalState: constantsXml.finalStateType.locked
                    }
                }
            });

            await this.modelWorkTime.findOneAndUpdate({employeeId: employeeLock.employeeId, logoutTime: moment.utc(0).toISOString()}, {
                $push: {
                    locked: {
                        start: moment.utc().toISOString(),
                        end: moment.utc(0).toISOString()
                    }
                }
            }).sort({loginTime: -1});

            res.status(200).json(employeeLock);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    unlock = async (req, res) => {
        try {
            const username = req.body.username;
            const userpass = req.body.password;

            const identifierType = 'ID';
            const credentials = {
                id: username,
                password: userpass
            };

            if (!credentials.id && !credentials.password) {
                return res.status(400).send('Invalid client request');
            }

            let employeeUnlock = await this.service.login(identifierType, credentials);

            if (!employeeUnlock) {
                return res.status(400).send('No connection to backend system');
            }

            if (employeeUnlock.hasOwnProperty('errorMessage') && employeeUnlock.errorMessage.includes('invalid password')) {
                let attempts = employeeUnlock.errorMessage.match(/\d+/g);
                let remainingAttempts = parseInt(attempts[1]) - parseInt(attempts[0]);

                if (employeeUnlock != null && remainingAttempts > 0) {
                    return res.status(400).send('Incorrect Password. Please try again. You have ' + remainingAttempts + ' remaining attempts before your account is locked.');
                }
            }

            if (employeeUnlock == null) {
                return res.status(400).send('Your account has been locked. Please contact the administrator.');
            }

            await this.posTransactionCtrl.create({
                function: 'logOnOff',
                component: 'mainCpuBoard',
                parameters: {
                    logOnOff: {
                        desiredState: constantsXml.desiredStateType.unlock,
                        logOnOffMethod: constantsXml.logOnOffMethodType.manual,
                        mode: (employeeUnlock.employeeRoleName == 'Maintenance') ? constantsXml.modeType.maintenanceMode : constantsXml.modeType.revenueMode,
                        finalState: constantsXml.finalStateType.loggedIn
                    }
                }
            });

            await this.modelWorkTime.findOneAndUpdate({employeeId: employeeUnlock.id, logoutTime: moment.utc(0).toISOString()}, {
                'locked.$[last].end': moment.utc().toISOString()
            }, {arrayFilters: [{'last.end': moment.utc(0).toISOString()}]}).sort({loginTime: -1});

            res.status(200).json(employeeUnlock);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    changePassword = async (req, res) => {
        try {
            const userId = req.body.userId;
            const newPassword = req.body.newPassword;

            const password = {
                newPassword: newPassword
            };

            const account = await this.service.changePassword(userId, password);

            res.status(200).json(account);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getUsersForTypePos = async (req, res) => {
        try {
            const deviceType = constants.deviceType.pos;
            const employee = await this.service.getUsersForType(deviceType);
            res.status(200).json(employee);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getUsersForTypeDriver = async (req, res) => {
        try {
            const deviceType = constants.deviceType.vehicleTicketingMachine;
            const employee = await this.service.getUsersForType(deviceType);
            res.status(200).json(employee);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    pushEmployee = async (req, res) => {
        try {
            const employee = req.body;
            const isBlocked = false;

            const employeesList = {
                employees: [{
                    id: employee.id,
                    firstName: employee.firstName,
                    lastName: employee.lastName,
                    smartCardId: (!isBlocked) ? employee.smartCardId : null,
                    externalReference: employee.externalReference,
                    shortNumber: (employee.shortNumber) ? employee.shortNumber : 0,
                    employeeRoleExternalId: (employee.employeeRoleExternalId) ? employee.employeeRoleExternalId : 0,
                    employeeRoleExternalReference: employee.employeeRoleExternalReference,
                    employeeRoleName: employee.employeeRoleName,
                    organizationExternalId: employee.organizationExternalId,
                    companyName: employee.companyName,
                    type: employee.type,
                    loginPassword: employee.loginPassword,
                    firstLogin: employee.firstLogin,
                    loginAttempts: employee.loginAttempts,
                    locked: employee.locked,
                    employeeLanguages: [ employee.employeeLanguages ],
                    lastChangedTimestamp: moment.utc(employee.lastChangedTimestamp).toISOString()
                }]
            };

            const pushEmployee = await this.service.pushEmployee(employeesList);

            res.status(200).json(pushEmployee);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    unblock = async (req, res) => {
        try {
            res.status(200).json(true);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getSessionToday = async (req, res) => {
        try {
            const employee = global.currentEmployeeData;
            const from = moment.utc(global.currentPosData.endOfWorkHour, this.timeFormat).utc().toISOString();
            const to = moment.utc().toISOString();

            const employeeSessions = await this.modelWorkTime.find({loginTime: {$gte: from}, logoutTime: {$lt: to}});
            const employeeSessionsTime = await this.getSessionActiveTime(employeeSessions);

            res.status(200).json(employeeSessionsTime);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getSessionHistory = async (req, res) => {
        try {
            const employee = global.currentEmployeeData;
            const from = moment.utc(global.currentPosData.endOfWorkHour, this.timeFormat).subtract(30, 'days').toISOString();
            const to = moment.utc().toISOString();

            const employeeSessions = await this.modelWorkTime.find({loginTime: {$gte: from}, logoutTime: {$lt: to}});
            const employeeSessionsTime = await this.getSessionActiveTime(employeeSessions);

            res.status(200).json(employeeSessionsTime);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    getSessionActiveTime = async (employees) => {
        try {
            let sessions = [];
            let employeesLen = employees.length;
            for (let i=0; i<employeesLen; i++) {

                let employeeSession = {
                    name: `${employees[i].firstName} ${employees[i].lastName}`,
                    loggedFrom: moment.utc(employees[i].loginTime).toISOString(),
                    loggedTo: (moment.utc(employees[i].logoutTime).toISOString() == moment.utc(0).toISOString()) ? moment.utc().toISOString() : moment.utc(employees[i].logoutTime).toISOString(),
                    duration: '0h 0min',
                    activeTime: '100/0'
                }

                let duration = moment.utc(employeeSession.loggedTo).diff(moment.utc(employeeSession.loggedFrom), 'minutes');
                let durationHour = Math.floor(duration / 60);
                let durationMinute = duration - (durationHour * 60);

                employeeSession.duration = `${durationHour}h ${durationMinute}min`;

                const employeeLock = employees[i].locked.filter((f) => moment.utc(f.end).toISOString() != moment.utc(0).toISOString());
                if (employeeLock.length) {
                    const durationLock = employeeLock.map((m) => moment.utc(m.end).diff(moment.utc(m.start), 'minutes')).reduce((p, n) => p + n, 0);
                    let percentageLock = Math.floor(durationLock / duration * 100);
                    employeeSession.activeTime = 100 - percentageLock + '/' + percentageLock;
                }

                sessions.push(employeeSession);
            }

            return sessions.sort((a, b) => b.loginTime - a.loginTime);
        } catch (err) {
            log.error(err);
        }
    }

    checkSupervisor = async (req, res) => {
        try {
            const username = req.body.username;
            const userpass = req.body.password;
            let isSupervisor = false;

            const identifierType = 'ID';
            const credentials = {
                id: username,
                password: userpass
            };

            if (!credentials.id && !credentials.password) {
                log.info('Invalid client request');
            }

            let employeeLogin = await this.service.login(identifierType, credentials);

            if (!employeeLogin) {
                log.info('No connection to backend system');
            }

            if (employeeLogin.hasOwnProperty('errorMessage') && employeeLogin.errorMessage.includes('invalid password')) {
                let attempts = employeeLogin.errorMessage.match(/\d+/g);
                let remainingAttempts = parseInt(attempts[1]) - parseInt(attempts[0]);

                if (employeeLogin != null && remainingAttempts > 0) {
                    log.info('Incorrect Password. Please try again. You have ' + remainingAttempts + ' remaining attempts before your account is locked.');
                } else {
                    log.info('Your account has been locked. Please contact the administrator.');
                }
            }

            if (employeeLogin.hasOwnProperty('errorMessage') && employeeLogin.errorMessage.includes('locked')) {
                log.info('Your account has been locked. Please contact the administrator.');
            }

            if (/supervisor/i.test(employeeLogin.employeeRoleName)) {
                isSupervisor = true;
            }

            res.status(200).json(isSupervisor);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

}

module.exports = EmployeeCtrl;
