let request = require('request');
let Twig = require('twig');
let email = require('../modules/email');
let mongo = require('../modules/database');
let collections = require('../config/collections');
let Crypto = require('crypto');
let projectModel = require('./project');

/** Notifications config **/
let config = require('../config/notifications');

module.exports = function () {
  const templatesPath = 'views/notifies/';
  const templates = {
    messenger: 'messenger.twig',
    email: 'email/notify.twig'
  };

  let timers = {};
  /* Time to wait before send notification about repeated events */
  const GROUP_TIME = config.GROUP_TIME;

  /**
   * Send notifications about new event using user settings
   *
   * Available services
   * - Telegram
   * - Slack
   * - Email
   *
   * @param project
   * @param event
   * @param times
   */
  let send_ = function (project, event, times) {
    if (!times) {
      return;
    }

    let users = [];

    return projectModel.getTeam(project._id)
      .then(function (team) {
        let queries = [];

        team.forEach(function (member) {
          let query = projectModel.getUserData(project._id, member.id)
            .then(function (userData) {
              userData.email = member.email;
              users.push(userData);
            });

          queries.push(query);
        });

        return Promise.all(queries);
      })
      .then(function () {
        users.forEach(function (userData) {
          let userId = userData.userId.toString(),
              renderParams = {
                event: event,
                project: project,
                serverUrl: process.env.SERVER_URL,
                times: times,
                userData: userData,
                unsubscribeHash: generateUnsubscribeHash(userId, project._id)
              };


          Twig.renderFile(templatesPath + templates.messenger, renderParams, function (err, html) {
            if (err) {
              logger.error('Can not render notify template because of ', err);
              return;
            }

            if (userData.notifies.tg && userData.tgHook) {
              request.post({url: userData.tgHook, form: {message: html, parse_mode: 'HTML'}});
            }

            if (userData.notifies.slack && userData.slackHook) {
              request.post({url: userData.slackHook, form: {message: html, parse_mode: 'HTML'}});
            }
          });

          if (userData.notifies.email) {
            Twig.renderFile(templatesPath + templates.email, renderParams, function (err, html) {
              if (err) {
                logger.error('Can not render notify template because of ', err);
                return;
              }

              let emailSubject = ' on ' + project.name + ': «' + event.message + '»';

              if (times > 1) {
                emailSubject = times + ' errors' + emailSubject;
              } else {
                emailSubject = 'Error' + emailSubject;
              }

              email.send(
                userData.email,
                emailSubject,
                '',
                html
              );
            });
          }
        });
      });
  };

  /**
   * Set new Timeout for event and send notification about first event.
   * If there are no more new events in timeout was set, notification will send by private send_ method
   * Otherwise, if new events are coming, Timeout will be restart
   *
   * @param project
   * @param event
   */
  let send = function (project, event) {
    /** getting a pack for this error with errors number */
    let timer = timers[event.groupHash];

    /** flag */
    let notFirstError = true;

    /** check if pack of same errors exists */
    if (timer) {
      /** increase number of errors */
      timer.times++;
      /** clear timeout for sending this pack */
      clearTimeout(timer.timeout);
    } else {
      /** notify about first event */
      send_(project, event, 1)
        .catch(function (e) {
          logger.error('Error while sending notification ', e);
        });
      notFirstError = false;

      /** create a new error pack with 0 errors */
      timer = timers[event.groupHash] = {
        times: 0
      };

      /** Remove timer if no error in GROUP_TIME seconds */
      setTimeout(function () {
        /** notify about pack of errors */
        if (timers[event.groupHash].times == 0) {
          delete timers[event.groupHash];
        };
      }, GROUP_TIME);
    }

    /** ready to send. if this error is not first need to set timeout */
    if (notFirstError) {
      timer.timeout = setTimeout(function () {
        /** notify about pack of errors */
        send_(project, event, timer.times)
          .catch(function (e) {
            logger.error('Error while sending notification ', e);
          });

        /**
         * If you don't want to notify about
         * first error this type never ever
         * then just RESET counter.
         */
        // timer = timers[event.groupHash] = {
        //   times: 0
        // };

        /**
         * If you want to notify user when this
         * error happens again firstly in a big
         * avalanche then REMOVE this error's pack.
         * It would be better for your server's RAM.
         */
        delete timers[event.groupHash];
      }, GROUP_TIME);
    }
  };

  /**
   * Generate unsubscribe hash from user id
   *
   * @param userId
   * @param projectId
   * @returns {String} generated hash
   */
  let generateUnsubscribeHash = function (userId, projectId) {
    let string = userId + process.env.SALT + projectId;

    let hash = Crypto.createHash('sha256').update(string, 'utf8').digest('hex');

    return hash;
  };

  /**
   * Set notifies.email flag in user profile to false
   *
   * @param userId
   * @param projectId
   * @param type
   */
  let unsubscribe = function (userId, projectId, type='email') {
    let field = 'notifies.' + type,
        collection = collections.MEMBERSHIP + ':' + userId;

    return mongo.updateOne(
      collection,
      {project_id: mongo.ObjectId(projectId)},
      {$set: {[field]: false}}
    );
  };

  return {
    send: send,
    unsubscribe: unsubscribe,
    generateUnsubscribeHash: generateUnsubscribeHash
  };
}();

@@ -4218,27 +4218,31 @@
  0%0A      %7D;%0A
-%0A
   
+  %7D%0A%0A
     /** Remo
@@ -4295,789 +4295,284 @@
     
-  setTimeout(function () %7B%0A        /** notify about pack of errors */%0A        if (timers%5Bevent.groupHash%5D.times == 0) %7B%0A          delete timers%5Bevent.groupHash%5D;%0A        %7D;%0A      %7D, GROUP_TIME);%0A    %7D%0A%0A    /** ready to send. if this error is not first need to set timeout */%0A    if (notFirstError) %7B%0A      timer.timeout = setTimeout(function () %7B%0A        /** notify about pack of errors */%0A        send_(project, event, timer.times)%0A          .catch(function (e) %7B%0A            logger.error('Error while sending notification ', e);%0A          %7D);%0A%0A        /**%0A         * If you don't want to notify about%0A         * first error this type never ever%0A         * then just RESET counter.%0A         */%0A        // timer = timers%5Bevent.groupHash%5D = %7B%0A        //   times: 0%0A        // %7D;%0A%0A  
+timer.timeout = setTimeout(function () %7B%0A%0A      if (notFirstError) %7B%0A        /** notify about pack of errors */%0A        send_(project, event, timer.times)%0A          .catch(function (e) %7B%0A            logger.error('Error while sending notification ', e);%0A          %7D);%0A      %7D%0A%0A
     
@@ -4577,18 +4577,16 @@
     /**%0A
-  
        *
@@ -4630,18 +4630,16 @@
 %0A       
-  
 * error 
@@ -4669,18 +4669,16 @@
 n a big%0A
-  
        *
@@ -4726,18 +4726,16 @@
 %0A       
-  
 * It wou
@@ -4777,23 +4777,19 @@
 %0A       
-  
 */%0A
-  
       de
@@ -4818,18 +4818,16 @@
 pHash%5D;%0A
-  
     %7D, G

Restructure timeout function