const db = require('../db/models');
const ReviewsDBApi = require('../db/api/reviews');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');

module.exports = class ReviewsService {
  static async updateBusinessRating(businessId, transaction) {
    if (!businessId) return;

    const reviews = await db.reviews.findAll({
      where: { businessId, status: 'PUBLISHED' },
      attributes: ['rating'],
      transaction,
    });

    if (reviews.length === 0) {
      await db.businesses.update({ rating: 0 }, { where: { id: businessId }, transaction });
      return;
    }

    const totalRating = reviews.reduce((sum, review) => sum + review.rating, 0);
    const averageRating = totalRating / reviews.length;

    await db.businesses.update({ rating: averageRating }, { where: { id: businessId }, transaction });
  }

  static async create(data, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      // Set status to PUBLISHED by default for now, or use PENDING if moderation is needed
      data.status = 'PUBLISHED';
      
      const reviews = await ReviewsDBApi.create(
        data,
        {
          currentUser,
          transaction,
        },
      );

      // Extract businessId from data or reviews object
      const businessId = data.business || (reviews.business && reviews.business.id) || data.businessId;
      await this.updateBusinessRating(businessId, transaction);

      await transaction.commit();
      return reviews;
    } 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 ReviewsDBApi.bulkImport(results, {
          transaction,
          ignoreDuplicates: true,
          validate: true,
          currentUser: req.currentUser
      });

      // After bulk import, we might need to update ratings for all affected businesses
      // For now, let's keep it simple.

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

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

      if (!review) {
        throw new ValidationError(
          'reviewsNotFound',
        );
      }

      // Ownership check for Verified Business Owner
      if (currentUser.app_role?.name === 'Verified Business Owner') {
          if (currentUser.businessId) {
              if (review.businessId !== currentUser.businessId) {
                  throw new ForbiddenError('forbidden');
              }
          } else {
              const business = await db.businesses.findByPk(review.businessId, { transaction });
              if (business && business.owner_userId !== currentUser.id) {
                  throw new ForbiddenError('forbidden');
              }
          }
      }

      const updatedReviews = await ReviewsDBApi.update(
        id,
        data,
        {
          currentUser,
          transaction,
        },
      );

      const businessId = (updatedReviews.business && updatedReviews.business.id) || updatedReviews.businessId || review.businessId;
      await this.updateBusinessRating(businessId, transaction);

      await transaction.commit();
      return updatedReviews;

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

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

    try {
      // Get businessIds before deleting
      const reviews = await db.reviews.findAll({
        where: { id: { [db.Sequelize.Op.in]: ids } },
        transaction,
      });

      // Ownership check for Verified Business Owner
      if (currentUser.app_role?.name === 'Verified Business Owner') {
          for (const review of reviews) {
              if (currentUser.businessId) {
                  if (review.businessId !== currentUser.businessId) {
                      throw new ForbiddenError('forbidden');
                  }
              } else {
                  const business = await db.businesses.findByPk(review.businessId, { transaction });
                  if (business && business.owner_userId !== currentUser.id) {
                      throw new ForbiddenError('forbidden');
                  }
              }
          }
      }

      const businessIds = [...new Set(reviews.map(r => r.businessId))];

      await ReviewsDBApi.deleteByIds(ids, {
        currentUser,
        transaction,
      });

      for (const businessId of businessIds) {
        await this.updateBusinessRating(businessId, transaction);
      }

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

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

    try {
      const review = await db.reviews.findByPk(id, { transaction });
      if (!review) {
          throw new ValidationError('reviewsNotFound');
      }

      // Ownership check for Verified Business Owner
      if (currentUser.app_role?.name === 'Verified Business Owner') {
          if (currentUser.businessId) {
              if (review.businessId !== currentUser.businessId) {
                  throw new ForbiddenError('forbidden');
              }
          } else {
              const business = await db.businesses.findByPk(review.businessId, { transaction });
              if (business && business.owner_userId !== currentUser.id) {
                  throw new ForbiddenError('forbidden');
              }
          }
      }

      const businessId = review.businessId;

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

      await this.updateBusinessRating(businessId, transaction);

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