const _ = require('lodash');
const uglifycss = require('uglifycss');
const colors = require('colors');
const lunr = require('lunr');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const async = require('async');
const nodemailer = require('nodemailer');
const sanitizeHtml = require('sanitize-html');
const escape = require('html-entities').AllHtmlEntities;
const mkdirp = require('mkdirp');
let ObjectId = require('mongodb').ObjectID;

const restrictedRoutes = [
    {route: '/admin/product/new', response: 'redirect'},
    {route: '/admin/product/insert', response: 'redirect'},
    {route: '/admin/product/edit/:id', response: 'redirect'},
    {route: '/admin/product/update', response: 'redirect'},
    {route: '/admin/product/delete/:id', response: 'redirect'},
    {route: '/admin/product/published_state', response: 'json'},
    {route: '/admin/product/setasmainimage', response: 'json'},
    {route: '/admin/product/deleteimage', response: 'json'},
    {route: '/admin/order/statusupdate', response: 'json'},
    {route: '/admin/settings/update', response: 'json'},
    {route: '/admin/settings/option/remove', response: 'json'},
    {route: '/admin/settings/pages/new', response: 'redirect'},
    {route: '/admin/settings/pages/edit/:page', response: 'redirect'},
    {route: '/admin/settings/pages/update', response: 'json'},
    {route: '/admin/settings/pages/delete/:page', response: 'redirect'},
    {route: '/admin/settings/menu/new', response: 'redirect'},
    {route: '/admin/settings/menu/update', response: 'redirect'},
    {route: '/admin/settings/menu/delete/:menuid', response: 'redirect'},
    {route: '/admin/settings/menu/save_order', response: 'json'},
    {route: '/admin/file/upload', response: 'redirect'},
    {route: '/admin/file/delete', response: 'json'}
];

// Allowed mime types for product images
exports.allowedMimeType = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/bmp',
    'image/webp'
];

exports.fileSizeLimit = 10485760;

// common functions
exports.restrict = (req, res, next) => {
    exports.checkLogin(req, res, next);
};

exports.checkLogin = (req, res, next) => {
    // if not protecting we check for public pages and don't checkLogin
    if(req.session.needsSetup === true){
        res.redirect('/admin/setup');
        return;
    }

    if(req.session.user){
        next();
        return;
    }
    res.redirect('/admin/login');
};

exports.cleanHtml = (html) => {
    return sanitizeHtml(html);
};

exports.mongoSanitize = (param) => {
    if(param instanceof Object){
        for(const key in param){
            if(/^\$/.test(key)){
                delete param[key];
            }
        }
    }
    return param;
};

exports.checkboxBool = (param) => {
    if(param && param === 'on'){
        return true;
    }
    return false;
};

// Middleware to check for admin access for certain route
exports.checkAccess = (req, res, next) => {
    const routeCheck = _.find(restrictedRoutes, {'route': req.route.path});

    // If the user is not an admin and route is restricted, show message and redirect to /admin
    if(req.session.isAdmin === false && routeCheck){
        if(routeCheck.response === 'redirect'){
            req.session.message = 'Unauthorised. Please refer to administrator.';
            req.session.messageType = 'danger';
            res.redirect('/admin');
            return;
        }
        if(routeCheck.response === 'json'){
            res.status(400).json({message: 'Unauthorised. Please refer to administrator.'});
        }
    }else{
        next();
    }
};

exports.showCartCloseBtn = (page) => {
    let showCartCloseButton = true;
    if(page === 'checkout' || page === 'pay'){
        showCartCloseButton = false;
    }

    return showCartCloseButton;
};

// adds products to sitemap.xml
exports.addSitemapProducts = (req, res, cb) => {
    let db = req.app.db;

    let config = exports.getConfig();
    let hostname = config.baseUrl;

    db.products.find({productPublished: 'true'}).toArray((err, products) => {
        let posts = [];
        if(err){
            cb(null, posts);
        }
        async.eachSeries(products, (item, callback) => {
            let post = {};
            let url = item._id;
            if(item.productPermalink){
                url = item.productPermalink;
            }
            post.url = hostname + '/' + url;
            post.changefreq = 'weekly';
            post.priority = 0.7;
            posts.push(post);
            callback(null, posts);
        }, () => {
            cb(null, posts);
        });
    });
};

exports.clearSessionValue = (session, sessionVar) => {
    let temp;
    if(session){
        temp = session[sessionVar];
        session[sessionVar] = null;
    }
    return temp;
};

exports.updateTotalCartAmount = (req, res) => {
    let config = exports.getConfig();

    req.session.totalCartAmount = 0;

    _(req.session.cart).forEach((item) => {
        req.session.totalCartAmount = req.session.totalCartAmount + item.totalItemPrice;
    });

    // under the free shipping threshold
    if(req.session.totalCartAmount < config.freeShippingAmount){
        req.session.totalCartAmount = req.session.totalCartAmount + parseInt(config.flatShipping);
        req.session.shippingCostApplied = true;
    }else{
        req.session.shippingCostApplied = false;
    }
};

exports.checkDirectorySync = (directory) => {
    try{
        fs.statSync(directory);
    }catch(e){
        try{
            fs.mkdirSync(directory);
        }catch(err){
           mkdirp.sync(directory);// error : directory & sub directories to be newly created
        }
    }
};

exports.getThemes = () => {
    return fs.readdirSync(path.join(__dirname, '../', 'views', 'themes')).filter(file => fs.statSync(path.join(path.join(__dirname, '../', 'views', 'themes'), file)).isDirectory());
};

exports.getImages = (dir, req, res, callback) => {
    let db = req.app.db;

    db.products.findOne({_id: exports.getId(dir)}, (err, product) => {
        if(err){
            console.error(colors.red('Error getting images', err));
        }

        // loop files in /public/uploads/
        glob('public/uploads/' + product.productPermalink + '/**', {nosort: true}, (er, files) => {
            // sort array
            files.sort();

            // declare the array of objects
            let fileList = [];

            // loop these files
            for(let i = 0; i < files.length; i++){
                // only want files
                if(fs.lstatSync(files[i]).isDirectory() === false){
                    // declare the file object and set its values
                    let file = {
                        id: i,
                        path: files[i].substring(6)
                    };
                    if(product.productImage === files[i].substring(6)){
                        file.productImage = true;
                    }
                    // push the file object into the array
                    fileList.push(file);
                }
            }
            callback(fileList);
        });
    });
};

exports.getConfigFilename = () => {
    let filename = path.join(__dirname, '../config', 'settings-local.json');
    if(fs.existsSync(filename)){
        return filename;
    }
    return path.join(__dirname, '../config', 'settings.json');
};

exports.getConfig = () => {
    let config = JSON.parse(fs.readFileSync(exports.getConfigFilename(), 'utf8'));
    config.customCss = typeof config.customCss !== 'undefined' ? escape.decode(config.customCss) : null;
    config.footerHtml = typeof config.footerHtml !== 'undefined' ? escape.decode(config.footerHtml) : null;
    config.googleAnalytics = typeof config.googleAnalytics !== 'undefined' ? escape.decode(config.googleAnalytics) : null;

    // set the environment for files
    config.env = '.min';
    if(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === undefined){
        config.env = '';
    }

    // setup theme
    config.themeViews = '';
    if(typeof config.theme === 'undefined' || config.theme === ''){
        config.theme = 'Cloth'; // Default to Cloth theme
    }

    config.themeViews = '../views/themes/' + config.theme + '/';

    // if db set to mongodb override connection with MONGODB_CONNECTION_STRING env var
    config.databaseConnectionString = process.env.MONGODB_CONNECTION_STRING || config.databaseConnectionString;

    return config;
};

exports.getPaymentConfig = () => {
    let siteConfig = this.getConfig();

    let config = [];
    if(fs.existsSync(path.join(__dirname, '../config/' + siteConfig.paymentGateway + '.json'))){
        config = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/' + siteConfig.paymentGateway + '.json'), 'utf8'));
    }

    return config;
};

exports.updateConfig = (fields) => {
    let settingsFile = exports.getConfig();

    _.forEach(fields, (value, key) => {
        settingsFile[key] = value;
        if(key === 'customCss_input'){
            settingsFile['customCss'] = escape.encode(uglifycss.processString(value));
        }
        if(key === 'footerHtml_input'){
            let footerHtml = typeof value !== 'undefined' || value === '' ? escape.encode(value) : '';
            settingsFile['footerHtml'] = footerHtml;
        }
        if(key === 'googleAnalytics_input'){
            let googleAnalytics = typeof value !== 'undefined' ? escape.encode(value) : '';
            settingsFile['googleAnalytics'] = googleAnalytics;
        }
    });

    // delete settings
    delete settingsFile['customCss_input'];
    delete settingsFile['footerHtml_input'];
    delete settingsFile['googleAnalytics_input'];

    if(fields['emailSecure'] === 'on'){
        settingsFile['emailSecure'] = true;
    }else{
        settingsFile['emailSecure'] = false;
    }

    if(!fields['menuEnabled']){
        settingsFile['menuEnabled'] = false;
    }else{
        settingsFile['menuEnabled'] = true;
    }

    if(fields['emailPort']){
        settingsFile['emailPort'] = parseInt(fields['emailPort']);
    }

    if(fields['flatShipping']){
        settingsFile['flatShipping'] = parseInt(fields['flatShipping']);
    }

    if(fields['freeShippingAmount']){
        settingsFile['freeShippingAmount'] = parseInt(fields['freeShippingAmount']);
    }

    if(fields['productsPerRow']){
        settingsFile['productsPerRow'] = parseInt(fields['productsPerRow']);
    }

    if(fields['productsPerPage']){
        settingsFile['productsPerPage'] = parseInt(fields['productsPerPage']);
    }

    // write file
    try{
        fs.writeFileSync(exports.getConfigFilename(), JSON.stringify(settingsFile, null, 4));
        return true;
    }catch(exception){
        return false;
    }
};

exports.getMenu = (db) => {
    return db.menu.findOne({});
};

// creates a new menu item
exports.newMenu = (req, res) => {
    const db = req.app.db;
    return exports.getMenu(db)
    .then((menu) => {
        // if no menu present
        if(!menu){
            menu = {};
            menu.items = [];
        }
        let newNav = {
            title: req.body.navMenu,
            link: req.body.navLink,
            order: Object.keys(menu.items).length + 1
        };

        menu.items.push(newNav);
        return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
        .then(() => {
            return true;
        });
    })
    .catch((err) => {
        console.log('Error creating new menu', err);
        return false;
    });
};

// delete a menu item
exports.deleteMenu = (req, res, menuIndex) => {
    const db = req.app.db;
    return exports.getMenu(db)
    .then((menu) => {
        // Remove menu item
        menu.items.splice(menuIndex, 1);
        return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
        .then(() => {
            return true;
        });
    })
    .catch(() => {
        return false;
    });
};

// updates and existing menu item
exports.updateMenu = (req, res) => {
    const db = req.app.db;
    return exports.getMenu(db)
    .then((menu) => {
        // find menu item and update it
        let menuIndex = _.findIndex(menu.items, ['title', req.body.navId]);
        menu.items[menuIndex].title = req.body.navMenu;
        menu.items[menuIndex].link = req.body.navLink;
        return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
        .then(() => {
            return true;
        });
    })
    .catch(() => {
        return false;
    });
};

exports.sortMenu = (menu) => {
    if(menu && menu.items){
        menu.items = _.sortBy(menu.items, 'order');
        return menu;
    }
    return{};
};

// orders the menu
exports.orderMenu = (req, res) => {
    const db = req.app.db;
    return exports.getMenu(db)
    .then((menu) => {
        // update the order
        for(let i = 0; i < req.body.navId.length; i++){
            _.find(menu.items, ['title', req.body.navId[i]]).order = i;
        }
        return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
        .then(() => {
            return true;
        });
    })
    .catch(() => {
        return false;
    });
};

exports.getEmailTemplate = (result) => {
    let config = this.getConfig();

    let template = fs.readFileSync(path.join(__dirname, '../public/email_template.html'), 'utf8');

    $ = cheerio.load(template);
    $('#brand').text(config.cartTitle);
    $('#paymentResult').text(result.message);
    if(result.paymentApproved === true){
        $('#paymentResult').addClass('text-success');
    }else{
        $('#paymentResult').addClass('text-danger');
    }
    $('#paymentMessage').text('Thanks for shopping with us. We hope you will shop with us again soon.');
    $('#paymentDetails').html(result.paymentDetails);

    return $.html();
};

exports.sendEmail = (to, subject, body) => {
    let config = this.getConfig();

    let emailSettings = {
        host: config.emailHost,
        port: config.emailPort,
        secure: config.emailSecure,
        auth: {
            user: config.emailUser,
            pass: config.emailPassword
        }
    };

    // outlook needs this setting
    if(config.emailHost === 'smtp-mail.outlook.com'){
        emailSettings.tls = {ciphers: 'SSLv3'};
    }

    let transporter = nodemailer.createTransport(emailSettings);

    let mailOptions = {
        from: config.emailAddress, // sender address
        to: to, // list of receivers
        subject: subject, // Subject line
        html: body// html body
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if(error){
            return console.error(colors.red(error));
        }
        return true;
    });
};

// gets the correct type of index ID
exports.getId = (id) => {
    if(id){
        if(id.length !== 24){
            return id;
        }
    }
    return ObjectId(id);
};

exports.getData = (req, page, query) => {
    let db = req.app.db;
    let config = exports.getConfig();
    let numberProducts = config.productsPerPage ? config.productsPerPage : 6;

    let skip = 0;
    if(page > 1){
        skip = (page - 1) * numberProducts;
    }

    if(!query){
        query = {};
    }

    query['productPublished'] = 'true';

    // Run our queries
    return Promise.all([
        db.products.find(query).skip(skip).limit(parseInt(numberProducts)).toArray(),
        db.products.count(query)
    ])
    .then((result) => {
        const returnData = {data: result[0], totalProducts: result[1]};
        return returnData;
    })
    .catch((err) => {
        throw new Error('Error retrieving products');
    });
};

exports.indexProducts = (app) => {
    // index all products in lunr on startup
    return new Promise((resolve, reject) => {
        app.db.products.find({}).toArray((err, productsList) => {
            if(err){
                console.error(colors.red(err.stack));
                reject(err);
            }

            // setup lunr indexing
            const productsIndex = lunr(function(){
                this.field('productTitle', {boost: 10});
                this.field('productTags', {boost: 5});
                this.field('productDescription');

                const lunrIndex = this;

                // add to lunr index
                productsList.forEach((product) => {
                    let doc = {
                        'productTitle': product.productTitle,
                        'productTags': product.productTags,
                        'productDescription': product.productDescription,
                        'id': product._id
                    };
                    lunrIndex.add(doc);
                });
            });

            app.productsIndex = productsIndex;
            console.log(colors.cyan('- Product indexing complete'));
            resolve();
        });
    });
};

exports.indexCustomers = (app) => {
    // index all products in lunr on startup
    return new Promise((resolve, reject) => {
        app.db.customers.find({}).toArray((err, customerList) => {
            if(err){
                console.error(colors.red(err.stack));
                reject(err);
            }

            // setup lunr indexing
            const customersIndex = lunr(function(){
                this.field('email', {boost: 10});
                this.field('name', {boost: 5});
                this.field('phone');

                const lunrIndex = this;

                // add to lunr index
                customerList.forEach((customer) => {
                    let doc = {
                        'email': customer.email,
                        'name': `${customer.firstName} ${customer.lastName}`,
                        'phone': customer.phone,
                        'id': customer._id
                    };
                    lunrIndex.add(doc);
                });
            });

            app.customersIndex = customersIndex;
            console.log(colors.cyan('- Customer indexing complete'));
            resolve();
        });
    });
};

exports.indexOrders = (app, cb) => {
    // index all orders in lunr on startup
    return new Promise((resolve, reject) => {
        app.db.orders.find({}).toArray((err, ordersList) => {
            if(err){
                console.error(colors.red('Error setting up products index: ' + err));
                reject(err);
            }

            // setup lunr indexing
            const ordersIndex = lunr(function(){
                this.field('orderEmail', {boost: 10});
                this.field('orderLastname', {boost: 5});
                this.field('orderPostcode');

                const lunrIndex = this;

                // add to lunr index
                ordersList.forEach((order) => {
                    let doc = {
                        'orderLastname': order.orderLastname,
                        'orderEmail': order.orderEmail,
                        'orderPostcode': order.orderPostcode,
                        'id': order._id
                    };
                    lunrIndex.add(doc);
                });
            });

            app.ordersIndex = ordersIndex;
            console.log(colors.cyan('- Order indexing complete'));
            resolve();
        });
    });
};

exports.fixProductDates = (products) => {
    let index = 0;
    products.forEach((product) => {
        products[index].productAddedDate = new Date();
        index++;
    });
    return products;
};

// start indexing products and orders
exports.runIndexing = (app) => {
    console.info(colors.yellow('Setting up indexes..'));

    return Promise.all([
        exports.indexProducts(app),
        exports.indexOrders(app),
        exports.indexCustomers(app)
    ])
    .catch((err) => {
        console.info(colors.yellow('Error setting up indexes', err));
        process.exit(2);
    });
};
