const map = require('lodash/map');
const filter = require('lodash/filter');
const resolvePath = require('resolve-path');
const fs = require('mz/fs');
const async = require('async');


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

    self.id = values.id || values._id || null;
    self.userId = values.userId || null;
    self.codeId = values.codeId || null;
    self.created = values.created || new Date();
    self.expirationDate = values.expirationDate || null;
    self.files = values.files || [];
  }
}


class UploadRepository
{
  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.addUser('admin', null, 'test', null);

        resolve();
      });
    });
  }


  insert(upload)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      let insertUpload = {
        created: upload.created || new Date(),
        userId: upload.userId,
        codeId: upload.codeId,
        expirationDate: upload.expirationDate,
        files: map(filter(upload.files,
                   (file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')),
                   (file) => { return { id: file.id, name: file.name, size: file.size } })
      };

      if (insertUpload.files.length)
      {
        self.store.insert(insertUpload, (err, dbUpload) =>
        {
          if (err)
          {
            reject(err);
            return;
          }

          resolve(dbUpload._id);
        });
      }
      else
      {
        reject();
      }
    });
  }


  list(userId)
  {
    var self = this;

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

        resolve(docs.map((dbUpload) =>
        {
          return new Upload(dbUpload);
        }));
      });
    });
  }


  listForCode(codeId)
  {
    var self = this;

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

        resolve(docs.map((dbUpload) =>
        {
          return new Upload(dbUpload);
        }));
      });
    });
  }


  get(uploadId)
  {
    var self = this;

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

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


  move(codeId, userId)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.update({ codeId: codeId }, {
        $set: {
          userId: userId
        }
      },
      (err, numAffected) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

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

        resolve();
      });
    });
  }


  delete(uploadId)
  {
    var self = this;

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

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

        let upload = new Upload(doc);

        self.store.remove({ _id: uploadId }, async (err, numRemoved) =>
        {
          if (err)
          {
            reject(err);
            return;
          }

          await Promise.all(upload.files.map(async (file) =>
          {
            if (!file.id) return;
            if (!(await self.fileExists(file.id)))
            {
              var fullpath = resolvePath(config.fileUpload.path, file.id);
              try
              {
                await fs.unlink(fullpath);
              }
              catch (err)
              {
                console.log('Failed to delete ' + fullpath);
              }
            }
          }));

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


  fileExists(fileId)
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      self.store.findOne({ 'files.id': fileId }, (err, doc) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        resolve(doc !== null);
      });
    });
  }


  deleteExpired()
  {
    var self = this;

    return new Promise((resolve, reject) =>
    {
      let now = new Date();

      self.store.find({ $where: function() { return this.expirationDate !== null && this.expirationDate < now }}, (err, docs) =>
      {
        if (err)
        {
          reject(err);
          return;
        }

        async.eachOfSeries(docs,
          async (doc) =>
          {
            console.log('Expired upload: ' + doc._id);
            await self.delete(doc._id);
          },
          (err) =>
          {
            if (err)
              reject(err);
            else
              resolve();
          });
        });
    });
  }
}


module.exports = {
  UploadRepository
}