const db = require('../models');

const { Op } = db.Sequelize;

class TenantAccess {
  static getNotFoundError(message = 'Item not found') {
    const error = new Error(message);
    error.code = 404;
    return error;
  }

  static hasGlobalAccess(currentUser) {
    return Boolean(currentUser?.app_role?.globalAccess);
  }

  static getCurrentOrganizationId(currentUser) {
    return (
      currentUser?.organization?.id ||
      currentUser?.organizations?.id ||
      currentUser?.organizationId ||
      currentUser?.organizationsId ||
      null
    );
  }

  static getOwnershipField(model) {
    if (model?.rawAttributes?.organizationId) {
      return 'organizationId';
    }

    if (model?.rawAttributes?.organizationsId) {
      return 'organizationsId';
    }

    if (model?.getTableName?.() === 'organizations') {
      return 'id';
    }

    return null;
  }

  static buildTenantWhere(model, where = {}, options = {}) {
    const ownershipField = this.getOwnershipField(model);

    if (!ownershipField) {
      return where;
    }

    const currentUser = options.currentUser;

    if (!currentUser || this.hasGlobalAccess(currentUser)) {
      return where;
    }

    const organizationId = this.getCurrentOrganizationId(currentUser);

    if (!organizationId) {
      throw this.getNotFoundError();
    }

    const tenantWhere = { [ownershipField]: organizationId };

    if (!where || Object.keys(where).length === 0) {
      return tenantWhere;
    }

    return {
      [Op.and]: [where, tenantWhere],
    };
  }

  static async findOne(model, where = {}, options = {}) {
    return model.findOne({
      where: this.buildTenantWhere(model, where, options),
      transaction: options.transaction,
    });
  }

  static async findByPk(model, id, options = {}) {
    return this.findOne(model, { id }, options);
  }

  static async findByPkOrThrow(model, id, options = {}) {
    const record = await this.findByPk(model, id, options);

    if (!record) {
      throw this.getNotFoundError();
    }

    return record;
  }

  static async findAllByIds(model, ids = [], options = {}) {
    const uniqueIds = [...new Set((ids || []).filter(Boolean))];

    if (!uniqueIds.length) {
      return [];
    }

    const records = await model.findAll({
      where: this.buildTenantWhere(
        model,
        {
          id: {
            [Op.in]: uniqueIds,
          },
        },
        options,
      ),
      transaction: options.transaction,
    });

    if (records.length !== uniqueIds.length) {
      throw this.getNotFoundError();
    }

    return records;
  }
}

module.exports = TenantAccess;
