var express = require("express");
var hbs = require('express-hbs');
require('handlebars-form-helpers').register(hbs.handlebars);
var nodemailer = require('nodemailer');
var courtsopenUtils = require('./courtsopenUtils.js');
var bodyParser = require("body-parser");
var app = express();
var winston = require('winston');
winston.add(winston.transports.File, { filename: './logs/courtsopen.log', maxsize: 5000000 });  // 5MB
var fileSystem = require('fs');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var db = require('./db-auth');  // Make this db-auth.../db already taken by PostgreSQL linnked container!!!

// Configure the local strategy for use by Passport.
// The local strategy require a `verify` function which receives the credentials
// (`username` and `password`) submitted by the user.  The function must verify
// that the password is correct and then invoke `cb` with a user object, which
// will be set at `req.user` in route handlers after authentication.
passport.use(new Strategy(
  function(username, password, cb) {
    db.users.findByUsername(username, function(err, user) {
      winston.info('trying to lookup user.');
      if (err) { winston.info('db.users.findByUsername error.'); return cb(err); }
      if (!user) { winston.info('bad user'); return cb(null, false); }
      if (user.password != password) { winston.info('bad pw'); return cb(null, false); }
      return cb(null, user);
    });
  }));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session.  The
// typical implementation of this is as simple as supplying the user ID when
// serializing, and querying the user record by ID from the database when
// deserializing.
passport.serializeUser(function(user, cb) {
  cb(null, user.id);
});
passport.deserializeUser(function(id, cb) {
  db.users.findById(id, function (err, user) {
    if (err) { return cb(err); }
    cb(null, user);
  });
});

// Setup email
var transporter = nodemailer.createTransport({
    service: 'Gmail',
    auth: {
        user: 'alertmonitorfl@gmail.com',
        pass: 'ZPW!KdxkdTSmyH99'
    }
});
var mailOptions = {
    from: 'Alert Monitor <alertmonitorfl@gmail.com>',
    //to: 'jody@kaplon.us,don@gettner.com',
    to: 'jody@kaplon.us',
    subject: 'Device did not wish me a GoodMorning or GoodEvening',
};

var pg = require("pg");
var conString = "postgres://courtsopen:courtsopen@db/courtsopen";

app.use(bodyParser.json());                             // Needed for JSON POST requests from Particle Cores.
app.use(bodyParser.urlencoded({ extended: false }));    // Needed for web POST requests from edit form.

// Use `.hbs` for extensions and find partials in `views/partials`.
app.engine('hbs', hbs.express4({
    partialsDir: __dirname + '/views/partials'
}));
app.set('view engine', 'hbs');
app.set('views', __dirname + '/views');
var favicon = require('serve-favicon');
app.use(favicon(__dirname + '/assets/favicon.ico'));  // Put this before setting static dir.
app.use(express.static('assets'));
app.use(require('cookie-parser')());
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));

// Initialize Passport and restore authentication state, if any, from the session.
app.use(passport.initialize());
app.use(passport.session());

// As with any middleware it is quintessential to call next() if the user is authenticated
var isAuthenticated = function (req, res, next) {
    var loc = req.params.loc;
    if (req.isAuthenticated()) {
        return next();
        res.redirect(req); // I think this is what I want, but might cause re-dir loop.
    } else {
        // If user NOT authenticated, redirect to login page, do not call next().
        res.redirect('/' + loc + '/login');
    }
}

/*******************************************************************************************************
With this express setup, ordering of routes matters!!! It's 1st-come-1st-served.
If a more specific route is placed after a more general route, the general route will be chosen.
Not like nginx where the most specific match always wins.
Using express.Router() in express-4 might fix this, as would converting to hapi...
But, for now, order routes like this:
    - static routes, ordered more specific to less specific
    - dynamic routes, ordered more specific to less specific
********************************************************************************************************/
app.get('/logout', function(req, res){
    req.logout();
    res.redirect('/'); // possible to go back to appropriate :loc?
});

app.get('/', function(req, res){
    winston.info("GET /");
   res.render('home', {}, function(err, html) {
        if(err !== null) {
            winston.error(err);
        } else {
            res.send(html);
        }
    });
});

app.post('/', function(req, res){
    var postEvent = req.body.postEvent;
    var source = req.body.source;
    winston.info(req.body);
    // If it's stripped down JSON sent by cell modem, there won't be a req.body.data key w/full-nested JSON as value.
    // If post body includes coreid and published_at, assume it's from a particle devcie and parse accordingly.
    if (req.body.coreid !== undefined && req.body.published_at !== undefined) {
        var innerDataJSON = JSON.parse(req.body.data);
        var status = JSON.stringify(innerDataJSON.status, null, 4).slice(1,-1);
        var coreid = JSON.stringify(req.body.coreid, null, 4).slice(1,-1);
        var pubAt = JSON.stringify(req.body.published_at, null, 4).slice(1,-1);
        pg.connect(conString, function(err, client, done) {
            if(err) { return winston.error('error fetching client from pool', err); }
            client.query(
                "INSERT INTO alerts (origjson, coreid, published_at, status) VALUES ($1, $2, $3, $4);",
                [JSON.stringify(req.body, null, 4), coreid, pubAt, status]
            );
            done();
        });
    } else if (req.body.loc !== undefined) {  // Assume POST from /admin template
        var coreid = '2a002b000947343432313031'  // Hard-code TT coreid for now, otherwise need to look up coreid from location value.
        var received_at = new Date(Date.now());
        received_at = received_at.toISOString();
        var status = req.body.status;
        winston.info(coreid + '; ' + received_at + '; ' + status + '; ' + JSON.stringify(req.body, null, 4));
        pg.connect(conString, function(err, client, done) {
            if(err) { return winston.error('error fetching client from pool', err); }
            client.query(
                "INSERT INTO alerts (origjson, coreid, published_at, status) VALUES ($1, $2, $3, $4);",
                [JSON.stringify(req.body, null, 4), coreid, received_at, status]
            );
            done();
        });
    } else { // parse for minimal data from cell modem.
        var deviceid = JSON.stringify(req.body.data, null, 4).slice(1,4);
        var statusCode = JSON.stringify(req.body.data, null, 4).slice(5,7);
        var statusFromCode;
        switch (statusCode) {
            case "01": statusFromCode = "Open"; break;
            case "02": statusFromCode = "Closed"; break;
            case "03": statusFromCode = "GoodMorning"; break;
            case "04": statusFromCode = "GoodEvening"; break;
            default: statusFromCode = "UnknownStatus"
        };
        var received_at = new Date(Date.now());
        received_at = received_at.toISOString();
        winston.info(statusFromCode + ' ' + received_at);
        pg.connect(conString, function(err, client, done) {
            if(err) { return winston.error('error fetching client from pool', err); }
            client.query(
                "INSERT INTO alerts (origjson, coreid, published_at, status) VALUES ($1, $2, $3, $4);",
                [JSON.stringify(req.body, null, 4), deviceid, received_at, statusFromCode]
            );
            done();
        });
    }
    res.status(204).send('POST received');
});

app.get('/:loc/admin', isAuthenticated,  function(req, res) {
    var loc = req.params.loc;
    winston.info('GET ' + loc + '/admin');
    if (loc !== 'tt') {
        res.status(404).send('Not found');
    } else {
        // TODO: load admin template
        //res.status(200).send('admin template here...');
        pg.connect(conString, function(err, client, done) {
            if(err) {
                return winston.error('error fetching client from pool', err);
            }
            var devIndexQry = 
            "select status, published_at " + 
            "from alerts " + 
            "where status in ('Open', 'Closed') " +
            "and coreid = '2a002b000947343432313031' " +
            "order by published_at desc " +
            "limit 2"
            client.query(devIndexQry, function(err, result) {
                //call `done()` to release the client back to the pool
                done();
                if(err) {
                    return winston.error('error running query', err);
                }
                // Loop over elements in rows array, convert ugly UTC times to pretty local times.
                result.rows.forEach(function(row){
                    row.loc = loc;
                    row.pubdate = courtsopenUtils.getLocDateFromUTC(row.published_at);
                    row.pubtime = courtsopenUtils.getLocTimeFromUTC(row.published_at);
                    if(row.status.toLowerCase().indexOf('closed') > -1){
                        row.statusclass = 'closed';
                        row.oppstatus = 'open';
                        row.prettystatus = 'Open';
                    } else { 
                        row.statusclass = 'open';
                        row.oppstatus = 'closed';
                        row.prettystatus = 'Closed';
                    }
                });
                res.render('admin', {values: result.rows}, function(err, html) {
                    if(err !== null) {
                        winston.error(err);
                    } else {
                        res.send(html);
                    }
                });
            });
        });
    }
});


app.get('/:loc/login', function(req, res){
    var loc = req.params.loc;
    winston.info('GET ' + loc + '/login');
    res.render('login', {loc}, function(err, html) {
        if(err !== null) {
            winston.error(err);
        } else {
            res.send(html);
        }
    });
});
  
app.post('/:loc/login', 
  passport.authenticate('local', { failureRedirect: '/:loc/login' }),
  function(req, res) {
    winston.info('sucessful login');
    //res.redirect('/:loc/admin'); // no worky, :loc is literal here, not repop by param.
    //res.redirect(req); // no worky, get https://courts.kaplon.us/[object%20Object]
    var loc = req.params.loc;
    res.redirect('/' + loc + '/admin'); // no worky, undefined here...how to include w/post?
});

app.get('/:loc/status', function(req, res) {
    var loc = req.params.loc;
    if (loc !== 'tt') {
        res.status(404).send('Not found');
    } else {
        winston.info('GET ' + loc + '/status');
        // Lookup most recent status from DB and return as JSON.
        pg.connect(conString, function(err, client, done) {
            if(err) {
                return winston.error('error fetching client from pool', err);
            }
            var mostRecentStatusQry = 
            "select origjson " + 
            "from alerts " + 
            "where status in ('Open', 'Closed') " +
            "and coreid = '2a002b000947343432313031' " +
            "order by published_at desc " +
            "limit 1"
            client.query(mostRecentStatusQry, function(err, result) {
                //call `done()` to release the client back to the pool
                done();
                if(err) {
                    return winston.error('error running query', err);
                }
                res.setHeader('Content-Type', 'application/json');
                res.send(JSON.stringify(result.rows[0].origjson));
            });
        });
    }
});

app.get('/:loc', function(req, res) {
    var loc = req.params.loc;
    if (loc !== 'tt') {
        res.status(404).send('Not found');
    } else {
        winston.info("GET /tt");
        pg.connect(conString, function(err, client, done) {
            if(err) {
                return winston.error('error fetching client from pool', err);
            }
            var devIndexQry = 
            "select status, published_at " + 
            "from alerts " + 
            "where status in ('Open', 'Closed') " +
            "and coreid = '2a002b000947343432313031' " +
            "order by published_at desc " +
            "limit 2"
            client.query(devIndexQry, function(err, result) {
                //call `done()` to release the client back to the pool
                done();
                if(err) {
                    return winston.error('error running query', err);
                }
                // Loop over elements in rows array, convert ugly UTC times to pretty local times.
                result.rows.forEach(function(row){
                    row.pubdate = courtsopenUtils.getLocDateFromUTC(row.published_at);
                    row.pubtime = courtsopenUtils.getLocTimeFromUTC(row.published_at);
                    if(row.status.toLowerCase().indexOf('closed') > -1){
                        row.statusclass = 'closed';
                    } else { row.statusclass = 'open'; }
                });
                res.render('location', {values: result.rows}, function(err, html) {
                    if(err !== null) {
                        winston.error(err);
                    } else {
                        res.send(html);
                    }
                });
            });
        });
    }
});

/*   // 08.29.2019, disable logging and email since Don turned off device.
setInterval(function() {
    // Check every hour to see if GoodMorning or GoodEvening has gone missing.
    pg.connect(conString, function(err, client, done) {
        if(err) {
            return winston.error('error fetching client from pool', err);
        }
        var deadManQry = "select published_at from alerts where to_timestamp(published_at, 'YYYY-MM-DD HH24:MI:SS') > (now() - interval '14.5 hours')  limit 1";
        client.query(deadManQry, function(err, result) {
            //call `done()` to release the client back to the pool
            done();
            if(err) {
                return winston.error('error running query', err);
            } else if (result.rowCount  == 0) {
                mailOptions.text = "It's been too long since the last data transmission from device. \n\n";
                winston.info(mailOptions.text);
                // Don't include any other details for now, will need to change DB query to get details on last message received.
                //mailOptions.text = mailOptions.text + 'Status message, ' + status + '\n';
                //mailOptions.text = mailOptions.text + 'Published at, ' + courtsopenUtils.getLocDateFromUTC(pubAt) + ' ' + courtsopenUtils.getLocTimeFromUTC(pubAt) + '\n';
                transporter.sendMail(mailOptions, function(error, info){
                    if(error){
                        winston.error(error);
                    }else{
                        winston.info('Message sent: ' + info.response);
                    }
                });
            }
        });
    });
}, 60 * 60 * 1000);
*/

app.listen(3000, function() {
    winston.info("Started on PORT 3000");
});