const async = require('async');
const nodemailer = require('nodemailer');
const Email = require('email-templates');
const fs = require('mz/fs');
const path = require('path');
const getPaths = require('get-paths');
const AbstractIntervalWorker = require('./abstractintervalworker');
const NotificationType = require('../repository/notification').NotificationType;
const _ = require('lodash');


function humanFileSize(bytes, si)
{
  var thresh = si ? 1000 : 1024;
  if(Math.abs(bytes) < thresh)
  {
    return bytes + ' B';
  }

  var units = si
    ? ['kB','MB','GB','TB','PB','EB','ZB','YB']
    : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
  var u = -1;

  do
  {
    bytes /= thresh;
    ++u;
  }
  while(Math.abs(bytes) >= thresh && u < units.length - 1);

  return bytes.toFixed(1) + ' ' + units[u];
}


class NotificationWorker extends AbstractIntervalWorker
{
  constructor(repository)
  {
    super();
    this.repository = repository;
  }


  async tick()
  {
    var self = this;

    var notifications = await self.repository.notifications.list();
    return new Promise((resolve, reject) =>
    {
      async.eachOfSeries(notifications,
        async (item) => { await self.handleNotification(item) },
        (err) =>
        {
          if (err)
            reject(err);
          else
            resolve();
        });
    })
  }


  async handleNotification(notification)
  {
    let self = this;

    let user = await self.repository.users.get(notification.userId);
    if (user === null || !user.email)
      return;

    switch (notification.type)
    {
      case NotificationType.UploadComplete:
        await self.sendUploadNotification(notification, user);
        break;

      case NotificationType.CodeMoved:
        await self.sendCodeMovedNotification(notification, user);
        break;
    }
  }


  getDefaultLocals(user)
  {
    return {
      // Data
      user: user,

      // Configuration
      adminUrl: config.notifications.adminUrl,

      // Helper functions
      humanFileSize: humanFileSize
    }
  }


  async sendUploadNotification(notification, user)
  {
    let self = this;
    let locals = self.getDefaultLocals(user);

    locals.upload = await self.repository.uploads.get(notification.uploadId);
    if (locals.upload === null)
      return;

    await self.sendNotification(notification, user, locals, 'uploadnotification');
  }


  async sendCodeMovedNotification(notification, user)
  {
    let self = this;
    let locals = self.getDefaultLocals(user);

    locals.uploads = await self.repository.uploads.listForCode(notification.codeId);
    if (locals.uploads === null || locals.uploads.length == 0)
      return;

    locals.fileCount = 0;
    _.forEach(locals.uploads, (upload) =>
    {
      locals.fileCount += upload.files.length;
    });

    locals.prevUser = {
      userId: null,
      name: ''
    };

    if (notification.metadata !== null)
    {
      let resolveUser = async (userId) =>
      {
        if (!userId)
          return null;

        let user = await self.repository.users.get(userId);
        if (user !== null)
        {
          return {
            userId: userId,
            name: user.name
          }
        }
      };

      locals.prevUser = await resolveUser(notification.metadata.prevUserId);
      locals.assignUser = await resolveUser(notification.metadata.assignUserId);
    }

    await self.sendNotification(notification, user, locals, 'movednotification');
  }


  async sendNotification(notification, user, locals, template)
  {
    let self = this;

    return new Promise((resolve, reject) =>
    {
      const email = new Email({
        message: {
          from: config.notifications.mail.from
        },

        send: true,
        preview: false,
        transport: nodemailer.createTransport(config.notifications.mail.transport),
        views: {
          options: {
            extension: 'ejs'
          }
        }
      });


      // Override the default template locator with one that checks
      // the custom folder first, then falls back to the configured folder
      let getTemplatePathFromRoot = function(root, view, ext)
      {
        return new Promise(async (resolve, reject) => {
          try {
            const paths = await getPaths(
              root,
              view,
              ext
            );
            const filePath = path.resolve(root, paths.rel);
            resolve({ filePath, paths });
          } catch (err) {
            reject(err);
          }
        });
      };

      email.getTemplatePath = async function(view)
      {
        try
        {
          return await getTemplatePathFromRoot(path.resolve('custom/emails'), view, this.config.views.options.extension);
        }
        catch (err)
        {
          return await getTemplatePathFromRoot(this.config.views.root, view, this.config.views.options.extension);
        }
      };


      email
        .send({
          template: template,
          message: {
            to: user.email
          },
          locals: locals
        })
        .then(async () =>
        {
          await self.repository.notifications.delete(notification.id);
          console.log('Notification sent to: ' + user.email);
          resolve();
        })
        .catch(async (error) =>
        {
          console.log(error);
          notification.attempt++;
          if (notification.attempt > config.notifications.maxAttempts)
            await self.repository.notifications.delete(notification.id);
          else
            await self.repository.notifications.update(notification);

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

module.exports = NotificationWorker;