const express = require('express');
const router = express.Router();
const config = require('config');
const fs = require('fs').promises;
const passport = require('passport');

const canonicalize = require('../../util/auth/canonicalize');

const ModelPath = '../../models/';
const User = require(ModelPath + 'User.js');
const Invite = require(ModelPath + 'Invite.js');


const authenticate = require('../../util/auth/authenticateRequest');
const verifyBody = require('../../util/verifyBody');
const rateLimit = require('../../util/rateLimit');

// Wraps passport.authenticate to return a promise
const passportAuthenticate = (req, res, next) => {
    return new Promise((resolve) => {
        passport.authenticate('local', (err, user) => {
            resolve(user);
        })(req, res, next);
    });
};

// Wraps passport session creation to return a promise
const passportLogin = (user, req) => {
    return new Promise((resolve) => {
        req.login(user, resolve);
    });
};

const registerParams = [
    {name: 'displayname', type: 'string', maxLength: config.get('User.Username.maxLength'), sanitize: true, restrict: new RegExp(config.get('User.Username.restrictedChars'))},
    {name: 'password', type: 'string'},
    {name: 'invite', type: 'string'}];

router.post('/register',
    rateLimit(config.get('RateLimit.register.window'), config.get('RateLimit.register.max')),
    verifyBody(registerParams),
    async (req, res) => {
        const username = canonicalize(req.body.displayname);

        // Retrieve invite and username status
        const [invite, usernameCount] = await Promise.all([
            Invite.findOne({code: req.body.invite}),
            User.countDocuments({username: username})
        ]);

        // Validate the invite
        if (!invite)
            return res.status(422).json({message: 'Invalid invite code.'});
        if (invite.used)
            return res.status(422).json({message: 'Invite already used.'});
        if (invite.expires != null && invite.expires < Date.now())
            return res.status(422).json({message: 'Invite expired.'});

        // Validate the username
        if (usernameCount !== 0)
            return res.status(422).json({message: 'Username in use.'});

        // Create the user object
        await User.register({
            username: username,
            displayname: req.body.displayname,
            scope: invite.scope,
            date: Date.now()
        }, req.body.password);

        // Update the invite as used
        await Invite.updateOne({code: invite.code}, {recipient: username, used: Date.now()});

        res.status(200).json({'message': 'Registration successful.'});
    });



const loginParams = [
    {name: 'displayname', type: 'string'},
    {name: 'password', type: 'string'}];

router.post('/login',
    rateLimit(config.get('RateLimit.login.window'), config.get('RateLimit.login.max')),
    verifyBody(loginParams),
    async (req, res, next) => {
        req.body.username = canonicalize(req.body.displayname);

        // Authenticate
        const user = await passportAuthenticate(req, res, next);
        if (!user)
            return res.status(401).json({'message': 'Unauthorized.'});

        // Create session
        await passportLogin(user, req);

        // Set session vars
        req.session.passport.displayname = user.displayname;
        req.session.passport.scope = user.scope;

        res.status(200).json({'message': 'Logged in.'});
    });



router.post('/logout', (req, res) => {
    if (!req.isAuthenticated())
        return res.status(400).json({message: 'Not logged in.'});

    req.logout();
    res.status(200).json({'message': 'Logged out.'});
});



router.get('/whoami', authenticate(), (req, res) => {
    res.status(200).json({
        username: req.username,
        displayname: req.displayname,
        scope: req.scope,
        key: req.key
    });
});



module.exports = router;