const db = require('../db/models');
const BusinessesDBApi = require('../db/api/businesses');
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');
const { v4: uuidv4 } = require('uuid');

module.exports = class BusinessesService {
  static _sanitize(data, currentUser) {
    const numericFields = ['lat', 'lng', 'reliability_score', 'response_time_median_minutes', 'rating'];
    numericFields.forEach(field => {
      if (data[field] === '') {
        data[field] = null;
      }
    });

    // Hide internal fields from client forms
    if (currentUser?.app_role?.name === 'Verified Business Owner') {
        const internalFields = [
            'tenant_key', 
            'owner_userId', 
            'owner_user', 
            'createdAt', 
            'updatedAt', 
            'created_at_ts', 
            'updated_at_ts', 
            'reliability_score', 
            'reliability_breakdown_json', 
            'hours_json',
            'is_claimed',
            'is_active'
        ];
        internalFields.forEach(field => {
            delete data[field];
        });
    }

    return data;
  }

  static async findBy(id, currentUser) {
    const business = await BusinessesDBApi.findBy({ id });

    if (!business) {
      throw new ValidationError('businessesNotFound');
    }

    // Ownership check for Verified Business Owner
    if (currentUser?.app_role?.name === 'Verified Business Owner') {
      // Allow viewing if owner, or if no owner (public search results might call this)
      // But the requirement says "only edit businesses where ownerUserId == currentUser.id"
      // findBy is often used for view/edit.
    }

    return business;
  }

  static async create(data, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      data = this._sanitize(data, currentUser);
      
      // For VBOs, force the owner to be the current user
      if (currentUser?.app_role?.name === 'Verified Business Owner') {
          data.owner_userId = currentUser.id;
          data.is_active = true; // Ensure new business owner listings are active
          
          // Auto-generate internal fields if missing
          if (!data.slug && data.name) {
              data.slug = data.name.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '') + '-' + uuidv4().substring(0, 4);
          }
          if (!data.tenant_key) {
              data.tenant_key = 'TENANT-' + uuidv4().substring(0, 8).toUpperCase();
          }
      }

      const business = await BusinessesDBApi.create(
        data,
        {
          currentUser,
          transaction,
        },
      );

      // Link business to user if they don't have one set yet
      if (currentUser?.app_role?.name === 'Verified Business Owner' && !currentUser.businessId) {
          await db.users.update({ businessId: business.id }, { 
              where: { id: currentUser.id },
              transaction 
          });
      }

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

  static async claim(id, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      const business = await db.businesses.findByPk(id, { transaction });
      if (!business) {
        throw new ValidationError('businessNotFound');
      }
      if (business.owner_userId) {
        throw new ValidationError('businessAlreadyClaimed');
      }

      // Check for pending claim
      const pendingRequest = await db.claim_requests.findOne({
          where: {
              businessId: id,
              userId: currentUser.id,
              status: 'PENDING'
          },
          transaction
      });
      if (pendingRequest) {
          throw new ValidationError('claimRequestPending');
      }

      // Create Claim Request
      const claim_request = await db.claim_requests.create({
          businessId: id,
          userId: currentUser.id,
          status: 'PENDING',
          createdById: currentUser.id
      }, { transaction });

      await transaction.commit();
      return claim_request;
    } 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")); 

      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 BusinessesDBApi.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 {
      data = this._sanitize(data, currentUser);

      let business = await BusinessesDBApi.findBy(
        {id},
        {transaction},
      );

      if (!business) {
        throw new ValidationError(
          'businessesNotFound',
        );
      }

      // Ownership check for Verified Business Owner
      if (currentUser?.app_role?.name === 'Verified Business Owner') {
          if (business.owner_userId !== currentUser.id && business.id !== currentUser.businessId) {
            throw new ForbiddenError('forbidden');
          }
          // Prevent transferring ownership or changing internal fields
          delete data.owner_user;
          delete data.owner_userId;
          delete data.slug;
          delete data.tenant_key;
          delete data.is_active;
          delete data.is_claimed;
      }

      const updatedBusinesses = await BusinessesDBApi.update(
        id,
        data,
        {
          currentUser,
          transaction,
        },
      );

      await transaction.commit();
      return updatedBusinesses;

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

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

    try {
      // Ownership check for Verified Business Owner
      if (currentUser?.app_role?.name === 'Verified Business Owner') {
          const records = await db.businesses.findAll({ 
              where: { 
                  id: { [db.Sequelize.Op.in]: ids },
                  [db.Sequelize.Op.and]: [
                      { owner_userId: { [db.Sequelize.Op.ne]: currentUser.id } },
                      { id: { [db.Sequelize.Op.ne]: currentUser.businessId || null } }
                  ]
              },
              transaction 
          });
          if (records.length > 0) {
              throw new ForbiddenError('forbidden');
          }
      }

      await BusinessesDBApi.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 {
      let business = await db.businesses.findByPk(id, { transaction });
      if (!business) throw new ValidationError('businessesNotFound');

      if (currentUser?.app_role?.name === 'Verified Business Owner' && business.owner_userId !== currentUser.id && business.id !== currentUser.businessId) {
          throw new ForbiddenError('forbidden');
      }

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

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

  
};