const db = require('../db/models');
const Job_logsDBApi = require('../db/api/job_logs');
const CustomersDBApi = require('../db/api/customers');
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 { isRestrictedPayrollUser } = require('../security/payrollAccess');

module.exports = class Job_logsService {
  static async ensureRestrictedPayTypeAccess(payTypeId, currentUser, transaction) {
    if (!isRestrictedPayrollUser(currentUser) || !payTypeId) {
      return;
    }

    const assignment = await db.employee_pay_types.findOne({
      where: {
        employeeId: currentUser.id,
        pay_typeId: payTypeId,
        active: true,
      },
      transaction,
    });

    if (!assignment) {
      throw new ValidationError('errors.forbidden.message');
    }
  }

  static async create(data, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      let customerId = data.customer;
      
      // If customer is a string and not a UUID, try to find or create
      if (typeof customerId === 'string' && customerId.length > 0 && !customerId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
        let customer = await db.customers.findOne({ where: { name: customerId }, transaction });
        if (!customer) {
            customer = await CustomersDBApi.create({ name: customerId }, { currentUser, transaction });
        }
        customerId = customer.id;
      }

      const jobPayload = { ...data, customer: customerId };

      if (isRestrictedPayrollUser(currentUser)) {
        jobPayload.employee = currentUser.id;
      }

      await Job_logsService.ensureRestrictedPayTypeAccess(jobPayload.pay_type, currentUser, transaction);

      const createdJob = await Job_logsDBApi.create(
        jobPayload,
        {
          currentUser,
          transaction,
        },
      );

      if (data.chemical_usages && Array.isArray(data.chemical_usages)) {
        for (const usage of data.chemical_usages) {
          if (usage.chemical_product && usage.quantity_used) {
            await db.job_chemical_usages.create({
              job_logId: createdJob.id,
              chemical_productId: usage.chemical_product,
              quantity_used: usage.quantity_used,
              notes: usage.notes || '',
              createdById: currentUser.id,
              updatedById: currentUser.id
            }, { transaction });
          }
        }
      }

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

  static async bulkImport(req, res, sendInvitationEmails = true, host) {
    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")); // convert Buffer to Stream

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

      await Job_logsDBApi.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 {
      let job_logs = await Job_logsDBApi.findBy(
        {id},
        {
          transaction,
          currentUser,
        },
      );

      if (!job_logs) {
        throw new ValidationError(
          'job_logsNotFound',
        );
      }

      if (isRestrictedPayrollUser(currentUser) && job_logs.employeeId !== currentUser.id) {
        throw new ValidationError('errors.forbidden.message');
      }
      
      let customerId = data.customer;
      
      // If customer is a string and not a UUID, try to find or create
      if (typeof customerId === 'string' && customerId.length > 0 && !customerId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
        let customer = await db.customers.findOne({ where: { name: customerId }, transaction });
        if (!customer) {
            customer = await CustomersDBApi.create({ name: customerId }, { currentUser, transaction });
        }
        customerId = customer.id;
      }

      const jobPayload = { ...data, customer: customerId };

      if (isRestrictedPayrollUser(currentUser)) {
        jobPayload.employee = currentUser.id;
      }

      await Job_logsService.ensureRestrictedPayTypeAccess(
        jobPayload.pay_type !== undefined ? jobPayload.pay_type : job_logs.pay_typeId,
        currentUser,
        transaction,
      );

      const updatedJob_logs = await Job_logsDBApi.update(
        id,
        jobPayload,
        {
          currentUser,
          transaction,
        },
      );

      await transaction.commit();
      return updatedJob_logs;

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

  static async deleteByIds(ids, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      await Job_logsDBApi.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 Job_logsDBApi.remove(
        id,
        {
          currentUser,
          transaction,
        },
      );

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