import basicAuth from 'basic-auth';
import bcrypt from 'bcryptjs';
import UserModel from '../models/user';
import globals from '../globals';


// appends req.auth(res, ['levels'], method = 'all')
// which returns sends error message and returns false if unauthorized, otherwise true
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}

module.exports = async (req, res, next) => {
  let givenMethod = '';  // authorization method given by client, basic taken preferred
  let user = {name: '', level: '', id: '', location: ''};               // user object

  // test authentications
  const userBasic = await basic(req, next);

  if (userBasic) {  // basic available
    givenMethod = 'basic';
    user = userBasic;
  }
  else {  // if basic not available, test key
    const userKey = await key(req, next);
    if (userKey) {
      givenMethod = 'key';
      user = userKey;
    }
  }

  req.auth = (res, levels, method = 'all') => {
    if (givenMethod === method || (method === 'all' && givenMethod !== '')) {  // method is available
      if (levels.indexOf(user.level) > -1) {  // level is available
        return true;
      }
      else {
        res.status(403).json({status: 'Forbidden'});
        return false;
      }
    }
    else {
      res.status(401).json({status: 'Unauthorized'});
      return false;
    }
  }

  req.authDetails = {
    method: givenMethod,
    username: user.name,
    level: user.level,
    id: user.id,
    location: user.location
  };

  next();
}


function basic (req, next): any {  // checks basic auth and returns changed user object
  return new Promise(resolve => {
    const auth = basicAuth(req);
    if (auth !== undefined) {  // basic auth available
      UserModel.find({name: auth.name}).lean().exec( (err, data: any) => {  // find user
        if (err) return next(err);
        if (data.length === 1) {  // one user found
          bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
            if (err) return next(err);
            if (res === true) {  // password correct
              resolve({
                level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
                name: data[0].name,
                id: data[0]._id.toString(),
                location: data[0].location
              });
            }
            else {
              resolve(null);
            }
          });
        }
        else {
          resolve(null);
        }
      });
    }
    else {
      resolve(null);
    }
  });
}

function key (req, next): any {  // checks API key and returns changed user object
  return new Promise(resolve => {
    if (req.query.key !== undefined) {  // key available
      UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => {  // find user
        if (err) return next(err);
        if (data.length === 1) {  // one user found
          resolve({
            level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0],
            name: data[0].name,
            id: data[0]._id.toString(),
            location: data[0].location
          });
          if (!/^\/api/m.test(req.url)){
            delete req.query.key;  // delete query parameter to avoid interference with later validation
          }
        }
        else {
          resolve(null);
        }
      });
    }
    else {
      resolve(null);
    }
  });
}