const fs = require('fs');
const path = require('path');
const moment = require('moment');
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 PosTransaction = require('../models/pos-transaction');
const PosTransactionServ = require('../services/pos-transaction');

const Pos = require('../models/pos');
const PosServ = require('../services/pos');
const FinancialTransaction = require('../models/financial-transaction');
const Employee = require('../models/employee');

class PosTransactionCtrl {
    constructor () {
        this.model = PosTransaction;
        this.service = new PosTransactionServ();

        this.posModel = Pos;
        this.posServ = new PosServ();
        this.ticketModel = FinancialTransaction.Ticket;
        this.financialTransactionModel = FinancialTransaction.FinancialTransaction;
        this.employeeWorkTimeModel = Employee.EmployeeWorkTime;

        this.dateRFormat = (['mok'].includes(process.env.CLIENT_NAME)) ? 'YYYY' : 'YYYYMMDD';
    }

    status = async () => {
        try {
            const unsentFolder = fs.readdirSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT));
            const unsentFolderLen = unsentFolder.length;

            if (unsentFolderLen) {
                for (let i=0; i<unsentFolderLen; i++) {
                    await this.statusCheck(unsentFolder[i]);
                }
            }
        } catch (err) {
            log.error(err);
        }
    }

    statusCheck = async (file) => {
        try {
            const transactionFiles = {
                transactionFiles: {
                    file: file
                }
            };

            const transactionCheckStatus = await this.service.status(transactionFiles);

            switch (transactionCheckStatus.file.status) {
                case 'Ok':
                case 'Duplicate':
                    fs.renameSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT, file), path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_SENT, file));
                    await this.model.findOneAndUpdate({ name: file }, {
                        status: constants.transacStatus.sent,
                        path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_SENT, file)
                    });
                    if (process.env.POS_TRANSACTIONS_DELETE_SENT == 'true') {
                        fs.unlinkSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_SENT, file));
                    }
                break;
                case 'Error':
                    fs.renameSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT, file), path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_CORRUPTED, file));
                    await this.model.findOneAndUpdate({ name: file }, {
                        status: constants.transacStatus.corrupted,
                        path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_CORRUPTED, file)
                    });
                break;
                case 'NotYetProcessed':
                case 'UnknownFile':
                    fs.renameSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT, file), path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNKNOWN, file));
                    await this.model.findOneAndUpdate({ name: file }, {
                        status: constants.transacStatus.unknown,
                        path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNKNOWN, file)
                    });
                break;
            }
        } catch (err) {
            log.error(err);
        }
    }

    clean = async () => {
        try {
            const folders = [
                path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_CORRUPTED),
                path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNKNOWN)
            ];
            const fileTypes = ['OPER', 'SUMM'];

            for (let i=0; i<folders.length; i++) {
                const files = fs.readdirSync(folders[i]);

                for (let f=0; f<files.length; f++) {
                    if (fileTypes.some((type) => files[f].startsWith(type))) {
                        fs.unlinkSync(path.join(folders[i], files[f]));
                    } else {
                        fs.renameSync(path.join(folders[i], files[f]), path.join(process.cwd(), '../POSFiles', files[f]));
                    }
                }
            }
        } catch (err) {
            log.error(err);
        }
    }

    send = async () => {
        try {
            const unsentFolder = fs.readdirSync(path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT));
            const unsentFolderLen = unsentFolder.length;

            if (unsentFolderLen) {
                for (let i=0; i<unsentFolderLen; i++) {
                    await this.uploadFile(unsentFolder[i]);
                }
            }
        } catch (err) {
            log.error(err);
        }
    }

    create = async (transactionFunction) => {
        try {
            const device = await this.posModel.findOne({});
            const timestamp = moment.utc().toISOString();

            const transactionFile = await this.createFile(transactionFunction, timestamp);

            let transactionCounter = 1;
            if (device.transactionCounter == undefined) {
                const lastTransactionCounter = await this.posServ.getLastTransactionCounter(global.currentPosData.deviceId);
                transactionCounter = (lastTransactionCounter.lastTransactionCounter) ? lastTransactionCounter.lastTransactionCounter : 1;
            }

            global.currentPosData.transactionCounter = (device.transactionCounter == undefined) ? transactionCounter : device.transactionCounter+1;

            await this.posModel.findOneAndUpdate({ deviceId: global.currentPosData.deviceId }, {
                transactionCounter: global.currentPosData.transactionCounter
            }, { upsert: true });

            await this.uploadFile(transactionFile.fileName);
        } catch (err) {
            log.error(err);
        }
    }

    createFile = async (transactionFunction, timestamp) => {
        try {
            const transactionFile = await this.service.create(transactionFunction, timestamp);

            await this.model.create({
                name: transactionFile.fileName,
                status: constants.transacStatus.unsent,
                path: transactionFile.filePath,
                created: moment.utc(timestamp).toISOString()
            });

            return transactionFile;
        } catch (err) {
            log.error(err);
        }
    }

    uploadFile = async (fileName) => {
        try {
            const transactionUploadStatus = await this.service.upload(fileName);

            await this.model.findOneAndUpdate({ name: fileName }, {
                status: transactionUploadStatus
            });
        } catch (err) {
            log.error(err);
        }
    }

    clear = async () => {
        try {
            if (parseInt(process.env.POS_DATABASE_PURGE_INTERVAL) > 0) {
                const daysOld = moment.utc().subtract(parseInt(process.env.POS_DATABASE_PURGE_INTERVAL), 'days').toISOString();

                await this.model.deleteMany({created: {$lt: daysOld}});
                await this.financialTransactionModel.deleteMany({transactionTime: {$lt: daysOld}});
                await this.ticketModel.deleteMany({issueTime: {$lt: daysOld}});
                await this.employeeWorkTimeModel.deleteMany({loginTime: {$lt: daysOld}});
            }
        } catch (err) {
            log.error(err);
        }
    }

    invoice = async (timestamp) => {
        try {
            const device = await this.posModel.findOne({});
            const terminalId = device.terminalId;
            const locationId = device.locationExternalId;
            const date = timestamp.format(this.dateRFormat);

            global.currentPosData.invoiceCounter = (device.invoiceCounter == undefined) ? 1 : device.invoiceCounter+1;
            global.currentPosData.invoiceDate =  (device.invoiceDate == undefined) ? moment.utc().utcOffset(global.currentPosData.utcOffset).format(this.dateRFormat).toString() : device.invoiceDate;

            if (global.currentPosData.invoiceDate != date) {
                global.currentPosData.invoiceCounter = 1;
                global.currentPosData.invoiceDate = moment.utc().utcOffset(global.currentPosData.utcOffset).format(this.dateRFormat).toString();
            }

            await this.posModel.findOneAndUpdate({ deviceId: global.currentPosData.deviceId }, {
                invoiceCounter: global.currentPosData.invoiceCounter,
                invoiceDate: global.currentPosData.invoiceDate
            }, { upsert: true });

            let invoice = [];
            if (['mok'].includes(process.env.CLIENT_NAME)) {
                invoice.push(locationId);
                invoice.push(terminalId);
            } else {
                invoice.push(terminalId);
                invoice.push(date);
            }
            invoice.push(global.currentPosData.invoiceCounter);

            return invoice.join('-');
        } catch (err) {
            log.error(err);
        }
    }

}

module.exports = PosTransactionCtrl;
