'use strict';

const express = require('express'),
  { sendVerifyMail, sendPasswordChangedMail, sendVerifiedMail } = 
    require('../lib/mail'),
  crypto = require('crypto');

const router = express.Router();
const autoAuthenticate = 1;

router.get('/', (req, res/*, next*/) => {
  console.log('GET /users/');

  return getSessionUser(req).then((user) => {
    if (typeof user === 'string') {
      return res.status(403).send({ message: user });
    }
    return res.status(200).send(user);
  });
});

router.put('/password', async (req, res) => {
  console.log('/users/password');
  const db = req.app.locals.db;

  const changes = {
    currentPassword: req.query.c || req.body.c,
    newPassword: req.query.n || req.body.n
  };

  if (!changes.currentPassword || !changes.newPassword) {
    return res.status(400).send(
      'Missing current password and/or new password.');
  }

  if (changes.currentPassword == changes.newPassword) {
    return res.status(400).send(
      'Attempt to set new password to current password.');
  }

  const user = await getSessionUser(req);
  if (typeof user === 'string') {
    return res.status(403).send({ message: user });
  }
  return db.sequelize.query('SELECT id FROM users ' +
    'WHERE uid=:username AND password=:password', {
    replacements: {
      username: user.username,
      password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64')
    },
    type: db.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(users) {
    if (users.length != 1) {
      return null;
    }
    return user;
  }).then(function(user) {
    if (!user) {
      console.log('Invalid password');
      /* Invalid password */
      res.status(401).send('Invalid password');
      return null;
    }

    return db.sequelize.query('UPDATE users SET password=:password WHERE uid=:username', {
      replacements: {
        username: user.username,
        password: crypto.createHash('sha256').update(changes.newPassword).digest('base64')
      }
    }).then(function() {
      console.log('Password changed for user ' + user.username + ' to \'' + changes.newPassword + '\'.');
      
      res.status(200).send(user);
      user.id = req.session.userId;
      return sendPasswordChangedMail(db, req, user);
    });
  });
});

router.get('/csrf', (req, res) => {
  const token = req.csrfToken();
  console.log('/users/csrf', token);
  res.json({ csrfToken: token });
});

router.post('/signup', function(req, res) {
  console.log('/users/signup');
  const db = req.app.locals.db;

  const user = {
    uid: req.body.email,
    familyName: req.body.familyName,
    firstName: req.body.firstName,
    password: req.body.password,
    email: req.body.email,
  };

  if (!user.uid 
    || !user.email
    || !user.password 
    || !user.familyName 
    || !user.firstName) {
    return res.status(400).send({
      message: 'Missing email address, password, and/or name.'
    });
  }

  user.password = crypto.createHash('sha256')
    .update(user.password).digest('base64');
  user.md5 = crypto.createHash('md5')
    .update(user.email).digest('base64');

  return db.sequelize.query('SELECT * FROM users WHERE uid=:uid', {
    replacements: user,
    type: db.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(async function(results) {
    if (results.length != 0 && results[0].mailVerified) {
      return res.status(400).send({
        message: 'Email address already used.'
      });
    }

    let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    if (!re.exec(user.email)) {
      const error = `Invalid email address: ${user.email}.`;
      console.log(error);
      return res.status(401).send({
        message: error
      });
    }
 
    try {
      if (results.length != 0) {
        await db.sequelize.query('UPDATE users ' +
          'SET mailVerified=0');
        req.session.userId = results[0].id;
      } else {
        let [, metadata] = await db.sequelize.query('INSERT INTO users ' + 
          '(uid,firstName,familyName,password,email,memberSince,' +
          'authenticated,md5) ' +
          'VALUES(:uid,:firstName,:familyName,:password,' +
          `:email,CURRENT_TIMESTAMP,${autoAuthenticate},:md5)`, {
          replacements: user
        });
        req.session.userId = metadata.lastID;
      }
      return getSessionUser(req).then(function(user) {
        if (typeof user === 'string') {
          return res.status(403).send({ message: user });
        }
        res.status(200).send(user);
        user.id = req.session.userId;
        return sendVerifyMail(db, req, user);
      });
    } catch (error) {
      console.error(error);
    }
  });
});

const getSessionUser = async (req) => {
  const db = req.app.locals.db;

  console.log(req.session);
  if (!req.session || !req.session.userId) {
    return 'Unauthorized. You must be logged in.';
  }

  let query = 'SELECT ' +
      'uid AS username,firstName,familyName,mailVerified,authenticated,memberSince,email,md5 ' +
      'FROM users WHERE id=:id';
  const results = await db.sequelize.query(query, {
    replacements: {
      id: req.session.userId
    },
    type: db.Sequelize.QueryTypes.SELECT,
    raw: true
  });
  
  if (results.length != 1) {
    return 'Invalid account.';
  }

  const user = results[0];

  if (!user.mailVerified) {
    user.restriction = user.restriction || 'Email address not verified.';
  } else if (!user.authenticated) {
    user.restriction = user.restriction || 'Accout not authorized.';
  }

  /* Strip out any fields that shouldn't be there.
   * The allowed fields are: */
  const allowed = [
    'maintainer', 'username', 'firstName', 'familyName', 'mailVerified', 'authenticated', 'name', 'email', 'restriction', 'md5'
  ];
  for (let field in user) {
    if (allowed.indexOf(field) == -1) {
      delete user[field];
    }
  }
  return user;
};

router.post('/verify-email', async (req, res) => {
  console.log('/users/verify-email');
  const key = req.body.token;
  const db = req.app.locals.db;

  let results = await db.sequelize.query(
    'SELECT * FROM authentications WHERE key=:key', {
      replacements: { key },
      type: db.sequelize.QueryTypes.SELECT
    }
  );

  let token;
  if (results.length == 0) {
    console.log('Invalid key. Ignoring.');
    return res.status(400).send({
      message: 'Invalid authentication token.'
    });
  }

  token = results[0];
  console.log(token);
  console.log('Matched token: ' + JSON.stringify(token, null, 2));
  switch (token.type) {
  case 'account-setup':
    return db.sequelize.query(
      'UPDATE users SET mailVerified=1 WHERE id=:userId', {
        replacements: token
      }
    ).then(function () {
      return db.sequelize.query(
        'DELETE FROM authentications WHERE key=:key', {
          replacements: { key }
        }
      );
    }).then(function () {
      return db.sequelize.query(
        'SELECT * FROM users WHERE id=:userId', {
          replacements: token,
          type: db.sequelize.QueryTypes.SELECT
        }
      );
    }).then(function (results) {
      if (results.length == 0) {
        return res.status(500).send({
          message: 'Internal authentication error.'
        });
      }
      return results[0];
    }).then((user) => {
      sendVerifiedMail(db, req, user);
      req.session.userId = user.id;
    }).then(() => {
      return getSessionUser(req).then((user) => {
        if (typeof user === 'string') {
          return res.status(403).send({ message: user });
        }
        return res.status(200).send(user);
      });
    });
  }
});

router.post('/signin', (req, res) => {
  console.log('/users/signin');
  const db = req.app.locals.db;

  let { email, password } = req.body;

  if (!email || !password) {
    return res.status(400).send({
      message: 'Missing email and/or password'
    });
  }

  console.log('Looking up user in DB.');
   
  let query = 'SELECT ' +
      'id,mailVerified,authenticated,' +
      'uid AS username,' +
      'familyName,firstName,email ' +
      'FROM users WHERE uid=:username AND password=:password';
  return db.sequelize.query(query, {
    replacements: {
      username: email,
      password: crypto.createHash('sha256').update(password).digest('base64')
    },
    type: db.Sequelize.QueryTypes.SELECT
  }).then(function(users) {
    if (users.length != 1) {
      return null;
    }
    let user = users[0];
    req.session.userId = user.id;
    return user;
  }).then(function(user) {
    if (!user) {
      console.log(email + ' not found (or invalid password.)');
      req.session.userId = null;
      return res.status(401).send({ 
        message: 'Invalid sign in credentials'
      });      
    }

    let message = 'Logged in as ' + user.email + ' (' + user.id + ')';
    if (!user.mailVerified) {
      console.log(message + ', who is not verified email.');
    } else if (!user.authenticated) {
      console.log(message + ', who is not authenticated.');
    } else {
      console.log(message);
    }

    return getSessionUser(req).then((user) => {
      if (typeof user === 'string') {
        return res.status(403).send({ message: user });
      }
      return res.status(200).send(user);
    });
  });
});

router.post('/signout', (req, res) => {
  console.log('/users/signout');
  if (req.session && req.session.userId) {
    req.session.userId = null;
  }
  res.status(200).send({});
});

module.exports = router;
