import { TreeNodeBase } from '@tomei/general';
import { StatusEnum } from '../../enum/status.enum';
import { ProductCategoryRepository } from './category-product.repository';
import { IProductCategory } from '../../interfaces/product-category-attr.interface';
import { LoginUser } from '@tomei/sso';
import { ApplicationConfig } from '@tomei/config';
import { ActionEnum, Activity } from '@tomei/activity-history';
import cuid from '../../helpers/cuid';
import { IProductCategoryUpdate } from '../../interfaces/product-category-update-attr.interface';
import { Op } from 'sequelize';
import { ProductProductCategory } from '../product-product-category/product-product-category';
import { PlatformCategoryMapping } from '../platform-category-mapping-product/platform-category-mapping-product';

interface IMappingInfoParam {
  platform: string;
  platformCategoryId: string;
  platformCategoryName: string;
}
export class ProductCategory extends TreeNodeBase<ProductCategory> {
  ObjectId: string;
  ObjectName: string;
  ObjectType = 'ProductCategory';
  TableName: string;
  Code: string;
  CodePath: string;
  ParentCode: string;
  Name: string;
  Description: string;
  isChildrenLoaded: boolean;
  isParentLoaded: boolean;
  private _Status = StatusEnum.ACTIVE;
  private _CreatedById: string;
  private _CreatedAt: Date;
  private _UpdatedById: string;
  private _UpdatedAt: Date;
  private static _Repository = new ProductCategoryRepository();

  get Status() {
    return this._Status;
  }

  get CreatedById() {
    return this._CreatedById;
  }

  get CreatedAt() {
    return this._CreatedAt;
  }

  get UpdatedById() {
    return this._UpdatedById;
  }

  get UpdatedAt() {
    return this._UpdatedAt;
  }

  private constructor(productCategory?: IProductCategory) {
    super();

    if (productCategory) {
      this.Code = productCategory.Code;
      this.CodePath = productCategory.CodePath;
      this.ParentCode = productCategory.ParentCode;
      this.Name = productCategory.Name;
      this.Description = productCategory.Description;
      this._Status = productCategory.Status;
      this._CreatedById = productCategory.CreatedById;
      this._CreatedAt = productCategory.CreatedAt;
      this._UpdatedById = productCategory.UpdatedById;
      this._UpdatedAt = productCategory.UpdatedAt;
    }
  }

  static async init(code?: string) {
    try {
      if (code) {
        const productCategory =
          await ProductCategory._Repository.findByPk(code);

        if (!productCategory) {
          throw new Error('Product Category not found.');
        }

        const data: IProductCategory = {
          Code: productCategory.Code,
          CodePath: productCategory.CodePath,
          ParentCode: productCategory.ParentCode,
          Name: productCategory.Name,
          Description: productCategory.Description,
          Status: productCategory.Status,
          CreatedById: productCategory.CreatedById,
          CreatedAt: productCategory.CreatedAt,
          UpdatedById: productCategory.UpdatedById,
          UpdatedAt: productCategory.UpdatedAt,
        };

        return new ProductCategory(data);
      }

      return new ProductCategory();
    } catch (error) {
      throw error;
    }
  }

  async loadChildren() {
    if (!this.Code) {
      throw new Error('Code is missing.');
    }

    const children = await ProductCategory._Repository.findAll({
      where: { ParentCode: this.Code },
      order: [['CreatedAt', 'ASC']],
    });

    this.children = children.map((child) => {
      return new ProductCategory(child);
    });

    this.isChildrenLoaded = true;
  }

  async loadParent() {
    if (!this.Code) {
      throw new Error('Code is missing.');
    }

    if (this.ParentCode) {
      if (this.ParentCode !== this.Code) {
        const parent = await ProductCategory._Repository.findByPk(
          this.ParentCode,
        );
        this.parent = new ProductCategory(parent);
      }
    }

    this.isParentLoaded = true;
  }

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

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

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

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

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

      this._CreatedById = loginUser.ObjectId;
      this._CreatedAt = new Date();
      this._UpdatedById = loginUser.ObjectId;
      this._UpdatedAt = new Date();

      const createdData = await ProductCategory._Repository.create(
        {
          Code: this.Code,
          Name: this.Name,
          Description: this.Description,
          CodePath: this.CodePath,
          ParentCode: this.ParentCode,
          Status: this.Status,
          CreatedById: this.CreatedById,
          CreatedAt: this.CreatedAt,
          UpdatedById: this.UpdatedById,
          UpdatedAt: this.UpdatedAt,
        },
        {
          transaction: dbTransaction,
        },
      );

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.CREATE;
      activity.Description = 'Create Product Category';
      activity.EntityType = 'ProductCategory';
      activity.EntityId = this.Code;
      activity.EntityValueBefore = JSON.stringify({});
      activity.EntityValueAfter = JSON.stringify(
        createdData.get({ plain: true }),
      );

      await activity.create(loginUser.ObjectId, dbTransaction);
    } catch (error) {
      throw error;
    }
  }

  public async update(
    payload: IProductCategoryUpdate,
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<void> {
    try {
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');

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

      const entityValueBefore = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        CodePath: this.CodePath,
        ParentCode: this.ParentCode,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      if (payload.Description) {
        this.Description = payload.Description;
      }
      if (payload.ParentCode) {
        this.ParentCode = payload.ParentCode;
      }
      if (payload.Status) {
        this._Status = payload.Status;
      }

      if (payload.Code) {
        this.Code = payload.Code;
        if (this.ParentCode === this.Code) {
          this.ParentCode = payload.Code;
        }
      }

      if (payload.Name) {
        this.Name = payload.Name;
        this.CodePath = await this.getPath();
        await this.checkAndUpdateChildrenCodePath(
          payload.Name,
          this.Name,
          dbTransaction,
        );
      }
      this._UpdatedById = loginUser.ObjectId;
      this._UpdatedAt = new Date();

      const entityValueAfter = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        CodePath: this.CodePath,
        ParentCode: this.ParentCode,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      ProductCategory._Repository.update(
        {
          ...entityValueAfter,
        },
        {
          where: {
            Code: entityValueBefore.Code,
          },
          transaction: dbTransaction,
        },
      );

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.UPDATE;
      activity.Description = 'Update Product Category';
      activity.EntityType = 'ProductCategory';
      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;
    }
  }

  private async checkAndUpdateChildrenCodePath(
    oldName: string,
    newName: string,
    dbTransaction: any,
  ) {
    const isLeaf = await this.isLeaf();
    if (isLeaf) {
      return;
    }

    const childrens = await ProductCategory._Repository.findAll({
      where: {
        CodePath: {
          [Op.like]: `${oldName} > %`,
        },
      },
      transaction: dbTransaction,
    });

    childrens.forEach(async (children) => {
      const newCodePath = children.CodePath.replace(oldName, newName);
      await children.update(
        { CodePath: newCodePath },
        { transaction: dbTransaction },
      );
    });
  }

  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,
        'ProductCategory - View',
      );

      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductCategory - View"`,
        );
      }

      // Retrieve listing
      const whereObj = {};

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

      let options: any = {
        where: whereObj,
        order: [['CreatedAt', 'DESC']],
        transaction: dbTransaction,
      };

      if (page && rows) {
        const offset = rows * (page - 1);
        options = {
          ...options,
          offset,
          limit: rows,
        };
      }

      const result = await ProductCategory._Repository.findAndCountAll(options);

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

  async delete(loginUser: LoginUser, dbTransaction?: any): Promise<void> {
    try {
      //call login user check privilege check if login user have privilege to delete
      const systemCode =
        ApplicationConfig.getComponentConfigValue('system-code');
      const isPrivileged = await loginUser.checkPrivileges(
        systemCode,
        'ProductCategory - Delete',
      );

      if (!isPrivileged) {
        throw new Error(
          `Forbidden Error, does not have right privilege - "ProductCategory - Delete"`,
        );
      }

      const entityValueBefore = {
        Code: this.Code,
        Name: this.Name,
        Description: this.Description,
        CodePath: this.CodePath,
        ParentCode: this.ParentCode,
        Status: this.Status,
        CreatedById: this.CreatedById,
        CreatedAt: this.CreatedAt,
        UpdatedById: this.UpdatedById,
        UpdatedAt: this.UpdatedAt,
      };

      const isProductAssigned =
        await ProductProductCategory.CheckProductAssignToCategory(
          this.Code,
          dbTransaction,
        );

      await this.loadChildren();

      const isGotSubCategory = this.children.length > 0;

      if (isProductAssigned || isGotSubCategory) {
        this._Status = StatusEnum.DEACTIVATED;

        const childrens = await ProductCategory._Repository.findAll({
          where: {
            CodePath: {
              [Op.like]: `${this.Name}%`,
            },
          },
          transaction: dbTransaction,
        });

        childrens.forEach(async (children) => {
          const productCategory = await ProductCategory.init(children.Code);
          await productCategory.update(
            {
              Status: StatusEnum.DEACTIVATED,
            },
            loginUser,
            dbTransaction,
          );
        });

        await ProductCategory._Repository.update(
          {
            Status: StatusEnum.DEACTIVATED,
          },
          {
            where: {
              Code: this.Code,
            },
            transaction: dbTransaction,
          },
        );

        return;
      } else {
        await ProductCategory._Repository.delete(this.Code, dbTransaction);

        const activity = new Activity();
        activity.ActivityId = cuid();
        activity.Action = ActionEnum.DELETE;
        activity.Description = 'Delete Product Category';
        activity.EntityType = 'ProductCategory';
        activity.EntityId = this.Code;
        activity.EntityValueBefore = JSON.stringify(entityValueBefore);
        activity.EntityValueAfter = JSON.stringify({});

        await activity.create(loginUser.ObjectId, dbTransaction);

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

  public async assignPlatfromMapping(
    loginUser: LoginUser,
    dbTransaction: any,
    mappingInfo: IMappingInfoParam,
  ) {
    try {
      const pcm = await new PlatformCategoryMapping();
      pcm.Code = this.Code;
      pcm.Platform = mappingInfo.platform;
      pcm.PlatformCategoryId = mappingInfo.platformCategoryId;
      pcm.PlatformCategoryName = mappingInfo.platformCategoryName;
      return pcm.create(loginUser, dbTransaction);
    } catch (error) {
      throw new Error(
        `An Error occured when retriving product collection: ${error.message}`,
      );
    }
  }

  public async getPlatformMapping(
    loginUser: LoginUser,
    dbTransaction: any,
    page?: number,
    row?: number,
  ) {
    try {
      return PlatformCategoryMapping.findAll(
        loginUser,
        dbTransaction,
        this.Code,
        page,
        row,
      );
    } catch (error) {
      throw new Error(
        `An Error occured when retriving product collection: ${error.message}`,
      );
    }
  }
}
