const db = require('../db/models');
const UsersDBApi = require('../db/api/users');
const processFile = require('../middlewares/upload');
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');

const InvitationEmail = require('./email/list/invitation');
const EmailSender = require('./email');
const AuthService = require('./auth');

module.exports = class UsersService {
  static async create(data, currentUser, sendInvitationEmails = true, host) {
    let transaction = await db.sequelize.transaction();

    let email = data.email;
    let emailsToInvite = [];
    try {
      if (email) {
        let user = await UsersDBApi.findBy({ email }, { transaction });
        if (user) {
          throw new ValidationError('iam.errors.userAlreadyExists');
        } else {
          await UsersDBApi.create(
            { data },

            {
              currentUser,
              transaction,
            },
          );
          emailsToInvite.push(email);
        }
      } else {
        throw new ValidationError('iam.errors.emailRequired');
      }
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
    if (emailsToInvite && emailsToInvite.length) {
      if (!sendInvitationEmails) return;

      AuthService.sendPasswordResetEmail(email, 'invitation', host);
    }
  }

  static async bulkImport(req, res, sendInvitationEmails = true, host) {
    const transaction = await db.sequelize.transaction();
    let emailsToInvite = [];

    try {
      await processFile(req, res);
      const bufferStream = new stream.PassThrough();
      const results = [];

      await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream

      await new Promise((resolve, reject) => {
        bufferStream
          .pipe(csv())
          .on('data', (data) => results.push(data))
          .on('end', () => {
            console.log('results csv', results);
            resolve();
          })
          .on('error', (error) => reject(error));
      });

      const hasAllEmails = results.every((result) => result.email);

      if (!hasAllEmails) {
        throw new ValidationError('importer.errors.userEmailMissing');
      }

      await UsersDBApi.bulkImport(results, {
        transaction,
        ignoreDuplicates: true,
        validate: true,
        currentUser: req.currentUser,
      });

      emailsToInvite = results.map((result) => result.email);

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }

    if (emailsToInvite && emailsToInvite.length && !sendInvitationEmails) {
      emailsToInvite.forEach((email) => {
        AuthService.sendPasswordResetEmail(email, 'invitation', host);
      });
    }
  }

  static async update(data, id, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      let users = await UsersDBApi.findBy({ id }, { transaction });

      if (!users) {
        throw new ValidationError('iam.errors.userNotFound');
      }

      const updatedUser = await UsersDBApi.update(
        id,
        data,

        {
          currentUser,
          transaction,
        },
      );

      await transaction.commit();
      return updatedUser;
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static async remove(id, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      if (currentUser.id === id) {
        throw new ValidationError('iam.errors.deletingHimself');
      }

      if (currentUser.app_role?.name !== config.roles.admin) {
        throw new ValidationError('errors.forbidden.message');
      }

      await UsersDBApi.remove(id, {
        currentUser,
        transaction,
      });

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }
};
