const map = require('lodash/map');
const AuthTokens = require('../authtokens');
const bcrypt = require('bcrypt');


class User
{
  constructor(values)
  {
    var self = this;

    self.id = values.id || values._id || null;
    self.username = values.username || null;
    self.name = values.name || null;
    self.password = values.password || null;
    self.email = values.email || null;
    self.auth = values.auth || [];

    if (values.hasOwnProperty('active'))
      self.active = (values.active === true);
    else
      self.active = true;

    self.created = values.created || new Date();
    self.createdByUserId =  values.createdByUserId || null;
  }

  hasAuth(token)
  {
    var self = this;

    return self.auth.includes(token);
  }
}


class UserRepository
{
  constructor(store)
  {
    var self = this;
    self.store = store;
  }


  init()
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      // Initialize database if empty
      self.store.count({}, (err, count) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        if (count == 0)
        {
          self.insert(new User({
              username: 'admin',
              name: 'Administrator',
              password: 'changeme',
              auth: AuthTokens.all
            }))
            .then(() => { resolve() })
            .catch((e) => { reject(e); });
        }
        else
          resolve();
      });
    });
  }


  getLoginUser(username, password)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.findOne({ $or: [{ username: username }, { email: username }], active: true }, (err, doc) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        if (doc == null)
        {
          resolve(null);
          return;
        }

        bcrypt.compare(password, doc.hashedPassword, (err, res) =>
        {
          if (err)
            reject(err)
          else
            resolve(res ? new User(doc) : null);
        });
      });
    });
  }

  list()
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.find({}, (err, docs) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        resolve(map(docs, (doc) => new User(doc)));
      });
    });
  }


  get(userId)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.findOne({ _id: userId }, (err, doc) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        resolve(doc !== null ? new User(doc) : null);
      });
    });
  }


  insert(user)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      bcrypt.hash(user.password, 10, function(err, hash)
      {
        if (err)
        {
          reject(err);
          return;
        }

        self.store.insert({
          username: user.username,
          name: user.name,
          email: user.email,
          hashedPassword: hash,
          created: user.created || new Date(),
          createdByUserId: user.createdByUserId,
          active: user.active,
          auth: user.auth || []
        }, (err, dbUser) =>
        {
          if (err)
          {
            reject(err);
            return;
          }

          resolve(dbUser._id);
        });
      });
    });
  }


  update(user)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      let doUpdate = (newPassword) =>
      {
        let updateObject = {
          username: user.username,
          name: user.name,
          email: user.email,
          active: user.active,
          auth: user.auth
        };

        if (newPassword !== null)
          updateObject.hashedPassword = newPassword;

        self.store.update({ _id: user.id }, { $set: updateObject },
          (err, numAffected) =>
          {
            if (err)
            {
              reject(err);
              return;
            }

            if (numAffected == 0)
            {
              reject();
            }

            resolve();
          });
      };

      if (user.password)
      {
        bcrypt.hash(user.password, 10, function(err, hash)
        {
          if (err)
          {
            reject(err);
            return;
          }

          doUpdate(hash);
        });
      }
      else
        doUpdate(null);
    });
  }


  delete(userId)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.remove({ _id: userId }, (err, numRemoved) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        resolve();
      });
    });
  }


  getNames()
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.find({}, (err, docs) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        var userNames = {};
        docs.forEach((dbUser) =>
        {
          userNames[dbUser._id] = dbUser.name
        });

        resolve(userNames);
      });
    });
  }
}


module.exports = {
  User,
  UserRepository
}