const db = require('../db/models');
const processFile = require('../middlewares/upload');
const ValidationError = require('../services/notifications/errors/validation');
const csv = require('csv-parser');
const stream = require('stream');

function createEntityService(DBApi, options = {}) {
  const entityName = options.entityName || 'Entity';

  return class GenericService {
    static async create(data, currentUser) {
      const transaction = await db.sequelize.transaction();
      try {
        const record = await DBApi.create(data, { currentUser, transaction });
        await transaction.commit();
        return record;
      } catch (error) {
        await transaction.rollback();
        throw error;
      }
    }

    static async bulkImport(req, res) {
      const transaction = await db.sequelize.transaction();

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

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

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

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

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

    static async update(data, id, currentUser) {
      const transaction = await db.sequelize.transaction();
      try {
        const record = await DBApi.findBy({ id }, { transaction });

        if (!record) {
          throw new ValidationError(`${entityName}NotFound`);
        }

        const updated = await DBApi.update(id, data, {
          currentUser,
          transaction,
        });
        await transaction.commit();
        return updated;
      } catch (error) {
        await transaction.rollback();
        throw error;
      }
    }

    static async deleteByIds(ids, currentUser) {
      const transaction = await db.sequelize.transaction();
      try {
        await DBApi.deleteByIds(ids, { currentUser, transaction });
        await transaction.commit();
      } catch (error) {
        await transaction.rollback();
        throw error;
      }
    }

    static async remove(id, currentUser) {
      const transaction = await db.sequelize.transaction();
      try {
        await DBApi.remove(id, { currentUser, transaction });
        await transaction.commit();
      } catch (error) {
        await transaction.rollback();
        throw error;
      }
    }
  };
}

module.exports = { createEntityService };
