import { IProductGroupAttr } from '../../interfaces/product-group-attr.interface';
import { StatusEnum } from './enum/status.enum';
import { ProductGroupRepository } from './group-product.repository';
import { LoginUser } from '@tomei/sso';
import { ApplicationConfig } from '@tomei/config';
import { ActionEnum, Activity } from '@tomei/activity-history';
import cuid from '../../helpers/cuid';
import { ProductProductGroup } from '../product-group-product/product-group-product';
import { Op } from 'sequelize';
import { ObjectBase } from '@tomei/general';

interface IProductGroupInitParam {
  code?: string;
  groupInfo?: IProductGroupAttr;
}

export class ProductGroup extends ObjectBase {
  ObjectId: string;
  ObjectName: string;
  TableName: string;
  ObjectType = 'ProductGroup';
  Code: string;
  Name: string;
  Description: string;
  private _Status = StatusEnum.ACTIVE;
  private _CreatedById: string;
  private _CreatedAt: Date;
  private _UpdatedById: string;
  private _UpdatedAt: Date;
  private static _Repo = new ProductGroupRepository();

  get Status() {
    return this._Status;
  }

  set Status(value: StatusEnum) {
    this._Status = value;
  }

  get CreatedById() {
    return this._CreatedById;
  }

  set CreatedById(value: string) {
    this._CreatedById = value;
  }

  get CreatedAt() {
    return this._CreatedAt;
  }

  set CreatedAt(value: Date) {
    this._CreatedAt = value;
  }

  get UpdatedById() {
    return this._UpdatedById;
  }

  set UpdatedById(value: string) {
    this._UpdatedById = value;
  }

  get UpdatedAt() {
    return this._UpdatedAt;
  }

  set UpdatedAt(value: Date) {
    this._UpdatedAt = value;
  }

  private constructor(productGroup?: IProductGroupAttr) {
    super();
    if (productGroup) {
      this.Code = productGroup.Code;
      this.Name = productGroup.Name;
      this.Description = productGroup.Description;
      this._Status = productGroup.Status;
      this._CreatedById = productGroup.CreatedById;
      this._CreatedAt = productGroup.CreatedAt;
      this._UpdatedById = productGroup.UpdatedById;
      this._UpdatedAt = productGroup.UpdatedAt;
    }
  }

  static async init(options?: IProductGroupInitParam) {
    try {
      if (options) {
        const { code, groupInfo } = options;
        if (code) {
          const productGroup = await ProductGroup._Repo.findByPk(code);
          if (!productGroup) {
            throw new Error('Product Group not found.');
          }
          return new ProductGroup(productGroup);
        } else if (!groupInfo) {
          throw new Error('Group info or code is required.');
        } else {
          return new ProductGroup(groupInfo);
        }
      } else {
        return new ProductGroup();
      }
    } catch (error) {
      throw error;
    }
  }

  public async create(loginUser: LoginUser, dbTransaction?: any): Promise<any> {
    try {
      // Privilege checking
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');

      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'ProductGroup - Create',
      );
      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductGroup - Create"`,
        );
      }

      // Fields Validation
      this.CreatedById = loginUser.ObjectId;
      this.CreatedAt = new Date();
      this.UpdatedById = loginUser.ObjectId;
      this.UpdatedAt = new Date();

      if (!this.Code) {
        throw new Error('Code is required.');
      }

      if (!this.Name) {
        throw new Error('Name is required.');
      }

      if (!this.Status) {
        this.Status = StatusEnum.ACTIVE;
      }

      // Inserting Data
      const groupProduct = await ProductGroup._Repo.create({
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      });

      // Record Activity History
      const entityValueAfter = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.CREATE;
      activity.Description = 'Create Product Group';
      activity.EntityType = 'ProductGroup';
      activity.EntityId = this.Code;
      activity.EntityValueBefore = JSON.stringify({});
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);
      await activity.create(loginUser.ObjectId, dbTransaction);

      return groupProduct;
    } catch (error) {
      throw error;
    }
  }

  public async update(
    loginUser: LoginUser,
    groupInfo: IProductGroupAttr,
    dbTransaction?: any,
  ): Promise<any> {
    try {
      // Privilege checking
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');

      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'ProductGroup - Update',
      );
      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductGroup - Update"`,
        );
      }

      // Preparing Data
      const EntityValueBefore = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      this.Code = groupInfo.Code;
      this.Name = groupInfo.Name;
      this.Description = groupInfo.Description;
      this.Status = groupInfo.Status;
      this.CreatedById = groupInfo.CreatedById;
      this.CreatedAt = groupInfo.CreatedAt;

      this.UpdatedById = loginUser.ObjectName;
      this.UpdatedAt = new Date();

      // Updating Data
      const group = await ProductGroup._Repo.findOne({
        where: { Code: this.Code },
        transaction: dbTransaction,
      });

      if (!group) {
        throw new Error('Product Group not found.');
      }

      group.Code = this.Code;
      group.Name = this.Name;
      group.Description = this.Description;
      group.Status = this.Status;
      group.CreatedById = this.CreatedById;
      group.CreatedAt = this.CreatedAt;
      group.UpdatedById = this.UpdatedById;
      group.UpdatedAt = this.UpdatedAt;

      const updatedInfo = await group.save({
        transaction: dbTransaction,
      });

      const entityValueAfter = groupInfo;

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.UPDATE;
      activity.Description = 'Update Product Group';
      activity.EntityType = 'ProductGroup';
      activity.EntityId = this.Code;
      activity.EntityValueBefore = JSON.stringify(EntityValueBefore);
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);
      await activity.create(loginUser.ObjectId, dbTransaction);
      return updatedInfo;
    } catch (error) {
      throw error;
    }
  }

  public async delete(loginUser: LoginUser, dbTransaction?: any): Promise<any> {
    try {
      // Privilege checking
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');

      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'ProductGroup - Delete',
      );
      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductGroup - Delete"`,
        );
      }

      //  Deleting Data
      const EntityValueBefore = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      const checkProduct = await ProductProductGroup.CheckProductAssignToGroup(
        this.Code,
        dbTransaction,
      );

      if (checkProduct) {
        this._Status = StatusEnum.DEACTIVATED;
        this.update(loginUser, {
          Code: this.Code,
          Name: this.Name,
          Description: this.Description,
          Status: this.Status,
          CreatedById: this.CreatedById,
          CreatedAt: this.CreatedAt,
          UpdatedById: this.UpdatedById,
          UpdatedAt: this.UpdatedAt,
        });
        return;
      }

      // Record Activity History
      const group = await ProductGroup._Repo.findOne({
        where: { Code: this.Code },
        transaction: dbTransaction,
      });

      if (!group) {
        throw new Error('Product Group not found.');
      }

      await ProductGroup._Repo.delete(this.Code, dbTransaction);

      const entityValueAfter = {};

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.DELETE;
      activity.Description = 'Delete Product Group';
      activity.EntityType = 'ProductGroup';
      activity.EntityId = this.Code;
      activity.EntityValueBefore = JSON.stringify(EntityValueBefore);
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);
      await activity.create(loginUser.ObjectId, dbTransaction);
      return;
    } catch (error) {
      throw error;
    }
  }

  static async findAll(
    loginUser: LoginUser,
    dbTransaction?: any,
    page?: number,
    rows?: number,
    search = {},
  ): Promise<any> {
    try {
      // Privilege checking
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');

      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'ProductGroup - View',
      );
      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductGroup - View"`,
        );
      }

      // Retrieve listing
      let result: any;
      if (page && rows) {
        const offset = rows * (page - 1);
        const options: any = {
          offset,
          limit: rows,
          order: [['CreatedAt', 'DESC']],
          transaction: dbTransaction,
        };

        const whereObj = {};

        Object.entries(search).forEach(([key, value]) => {
          if (key === 'Status') {
            whereObj[key] = value;
          } else {
            whereObj[key] = {
              [Op.substring]: value,
            };
          }
        });

        if (!whereObj['Status']) {
          whereObj['Status'] = {
            [Op.ne]: StatusEnum.DEACTIVATED,
          };
        }

        if (whereObj) {
          options.where = whereObj;
        }

        result = await ProductGroup._Repo.findAndCountAll(options);
      } else {
        result = await ProductGroup._Repo.findAndCountAll();
      }

      return result;
    } catch (error) {
      throw error;
    }
  }
}
