var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.text());   // Use defaults for now, size limit is 100kb.
app.use(bodyParser.urlencoded({ extended: true }));   // Also need url encoding to handle login form.
var winston = require('winston');
winston.add(winston.transports.File, { filename: './logs/notes.kaplon.us.log', maxsize: 5000000 });  // 5MB
var fileSystem = require('fs');
var favicon = require('serve-favicon');
app.use(favicon(__dirname + '/assets/favicon.ico'));  // Put this before setting static dir.
app.use(express.static(__dirname + '/assets'));

/*-----------------------------------------
Ordering of these configs is important, don't shuffle them around.
------------------------------------------*/
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var db = require('./db');
var Session = require('express-session');
var SessionStore = require('session-file-store')(Session);
var session = Session({
        secret: 'here kitty kitty',
        resave: false,
        saveUninitialized: false,
        cookie: { sameSite: true, secure: true },
        store: new SessionStore({path: __dirname+'/tmp/sessions'})
    });
app.use(session);
app.set('trust proxy', true);

//----------------------------
// 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);
  });
});

// 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) {
    if (req.isAuthenticated()) {
        return next();
        res.redirect('/');
    } else {
        // If user NOT authenticated, redirect to login page, do not call next().
        res.redirect('/login');
    }
}

app.get('/', isAuthenticated, function(req, res){
    winston.info("GET /");
    // Respond with static file, contents will be loaded via websocket.
    res.sendFile('index.html', { root: __dirname + '/views/' });
});

app.get('/login', function(req, res){
    winston.info('GET /login');
    res.sendFile('login.html', { root: __dirname + '/views/' });
});
  
app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    winston.info('sucessful login for user, ' + req.user.username);
    res.redirect('/');
});
  
app.get('/logout', function(req, res){
    req.logout();
    res.redirect('/');
});

var http = require('http').Server(app);
var io = require('socket.io')(http, { cookie: false });
var iosess = require('socket.io-express-session');
io.use(iosess(session));

io.on('connection', function(socket){
    //winston.info('a user connected');
    winston.info('session id, '+ socket.handshake.session.id);
    // next line no worky...but i can get by with only user-#.
    //winston.info('user name, '+ socket.handshake.session.passport.user.username);
    try { 
        var userNum = socket.handshake.session.passport.user; 
    } catch (ex) {
        // Send redirect to client, socket connection will close when client goes back to /login.
        // But disconnect just in case client is misbehaving.
        winston.info('Session DNE, redirect and disconnect.');
        socket.emit('redirect', '/login');
        socket.disconnect();
    }
    winston.info('user id, '+ userNum);
    socket.join(userNum);
    var notePath, noteDir
    if (userNum === 1) {
        notePath = __dirname + '/note-data/allNotes.txt';
        noteDir = __dirname + '/note-data/';
    } else {
        notePath = __dirname + '/note-data/' + userNum + '/allNotes.txt';
        noteDir = __dirname + '/note-data/' + userNum + '/';
    }
    fileSystem.readFile(notePath, {encoding: 'utf-8'}, function(err,data){
        if (!err){
            winston.info('file read on connection');
            socket.emit('download allNotes',data);  // Send content only to newly connected client.
        } else { winston.error(err); }
    });
    socket.on('upload allNotes', function(msg){
        winston.info('WS data received from user # '+ userNum);
        var now = Date.now();
        // Overwrite allNotes.txt with new contents from client.
        fileSystem.readFile(notePath, 'utf-8', function(err, data){
            if (err) { 
                winston.error(err); 
            } else {
                fileSystem.writeFile(notePath, msg, 'utf-8', function(err) {
                    if (err) { winston.error(err); }
                    var exec = require('child_process').exec;
                    var cmd = `
                        cd ${noteDir} && \
                        git checkout -b ${now} && \
                        git commit -am "Notes modified via websocket, ${now}" && \
                        git checkout master && \
                        git merge ${now}
                    `;
                    //winston.info(cmd);
                    exec(cmd, { uid: 1000 }, function(error, stdout, stderr) {
                        if (error) { winston.error(error); }
                        if (!stdout.includes('CONFLICT')) {   // Case-sensitivity seems brittle here. Better to check return code rather than stdout? Can child_process be called w/signature to include return code?
                            var cmd2 = `cd ${noteDir} && git branch -d ${now}`
                            exec(cmd2, function(error, stdout, stderr) {
                                if (error) { winston.error(error); }
                                winston.info('temp branch deleted; new content written to allNotes.txt');
                                // Send new content to all other clients EXCEPT the orig sender.
                                socket.in(userNum).emit('download allNotes',msg);
                                winston.info('sent content update to other clients');
                            });
                        } else {
                            // Auto-merge failed, keep the new branch.
                            // Send a conflict warning message to socket.io clients (prompt for IAP ;-).
                            socket.in(userNum).emit('conflict', `Auto-merge failed, manually merge ${now} branch to rescue recent edit.`);
                            socket.in(userNum).emit('download allNotes',data);  // Send pre-conflict content back down to client.
                            winston.info(`Auto-merge failed, manually merge ${now} branch to rescue recent edit.`);
                            // MVP for now, can deal w/merge conflicts with git on the server if the cotent needs to be saved.
                        }
                        winston.info(stdout);
                    });
                });
            }
        });
    });
    socket.on('cm-save', function(msg){
        winston.info(msg);
    });
});

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