import { ProductWithInventoryBase } from '../../base/product-with-inventory/product-with-inventory.base';
import { ClassError, RecordNotFoundError } from '@tomei/general';
import { ProductJewelleryRepository } from './jewellery-product.repository';
import { IJewelleryProductAttr } from '../../interfaces/product-jewellery-attr.interface';
import { ProductWithInventoryModel } from '../../entities/product-with-inventory.entity';
import { ProductJewelleryModel } from '../../entities/product-jewellery.entity';
import { ProductModel } from '../../entities/product.entity';
import cuid from '../../helpers/cuid';
import { LoginUser } from '@tomei/sso';
import { StockProfileModel, StockProfile } from '@tomei/stock';
import { Op } from 'sequelize';
import { ActionEnum, Activity } from '@tomei/activity-history';
import { SettingsCategoryModel } from '../../entities/settings-category.entity';
import { SettingsCollectionModel } from '../../entities/settings-collection.entity';
import { SettingsGroupModel } from '../../entities/settings-group.entity';
import { ProductCertificateModel } from '../../entities/product-certificate.entity';
import { ProductTagModel } from '../../entities/product-tag.entity';
import { ProductBrandModel } from '../../entities/product-brand.entity';
import { ProductCustomizeOptionModel } from '../../entities/product-customize-option.entity';
import { ProductMaterialModel } from '../../entities/product-material.entity';
import { IProductVariantCreate } from '../../interfaces/product-variant-create.interface';
import { ProductVariantWithInventory } from '../variant-product-with-inventory/variant-product-with-inventory';
import { ProductVariantType } from '../../enum/product-variant.enum';

interface IProductJewelleryInitParam {
  productId?: string;
  productInfo?: IJewelleryProductAttr;
}

export class ProductJewellery extends ProductWithInventoryBase {
  ObjectType = 'ProductJewellery';
  private _ProfileId: string;
  private _Surface: string;
  private _Uniqueness: string;
  private _HasCertificateYN = 'N';
  private static _ProductJewelleryRepository = new ProductJewelleryRepository();

  get ProductId() {
    return super.ProductId;
  }

  set ProductId(productId: string) {
    super.ProductId = productId;
  }

  get ProfileId() {
    return this._ProfileId;
  }

  private set ProfileId(id: string) {
    this._ProfileId = id;
  }

  get Surface() {
    return this._Surface;
  }

  set Surface(surface: string) {
    this._Surface = surface;
  }

  get Uniqueness() {
    return this._Uniqueness;
  }

  set Uniqueness(uniqueness: string) {
    this._Uniqueness = uniqueness;
  }

  get HasCertificateYN() {
    return this._HasCertificateYN;
  }

  set HasCertificateYN(hasCertificateYN: string) {
    this._HasCertificateYN = hasCertificateYN;
  }

  get UOM() {
    return super.UOM;
  }

  get TotalUnits() {
    return super.TotalUnits;
  }

  get TotalUnitsAvailable() {
    return super.TotalUnitsAvailable;
  }

  get TotalUnitsInCurrentOrder() {
    return super.TotalUnitsInCurrentOrder;
  }

  get TotalUnitsSold() {
    return super.TotalUnitsSold;
  }

  get TotalUnitsReserved() {
    return super.TotalUnitsReserved;
  }

  get TotalUnitsOnConsignment() {
    return super.TotalUnitsOnConsignment;
  }

  get TotalUnitsUnderMaintenance() {
    return super.TotalUnitsUnderMaintenance;
  }

  get TotalUnitsVoid() {
    return super.TotalUnitsVoid;
  }

  get TotalUnitsInTransit() {
    return super.TotalUnitsInTransit;
  }

  get TotalUnitsBackOrdered() {
    return super.TotalUnitsBackOrdered;
  }

  get TotalUnitsPreOrdered() {
    return super.TotalUnitsPreOrdered;
  }

  get StockLowAlertLevel() {
    return super.StockLowAlertLevel;
  }

  get StockReorderLevel() {
    return super.StockReorderLevel;
  }

  get BufferStockLevel() {
    return super.BufferStockLevel;
  }

  protected constructor(productJewelleryAttr) {
    super(productJewelleryAttr);
    this._ProfileId = productJewelleryAttr.ProfileId;
    this._Surface = productJewelleryAttr.Surface;
    this._Uniqueness = productJewelleryAttr.Uniqueness;
    this._HasCertificateYN = productJewelleryAttr.HasCertificateYN;
  }

  static async init(
    options: IProductJewelleryInitParam,
  ): Promise<ProductJewellery> {
    try {
      const { productId, productInfo } = options;
      if (productId) {
        const product =
          await ProductJewellery._ProductJewelleryRepository.findOne({
            where: {
              ProductId: productId,
            },
            include: [
              {
                model: ProductWithInventoryModel,
                include: [
                  {
                    model: ProductModel,
                  },
                ],
              },
            ],
          });

        const productAttr: IJewelleryProductAttr = {
          ProductId: product.ProductId,
          Name: product.ProductWithInventory.Product.Name,
          Description: product.ProductWithInventory.Product.Description,
          SKU: product.ProductWithInventory.Product.SKU,
          Type: product.ProductWithInventory.Product.Type,
          Remark: product.ProductWithInventory.Product.Remark,
          IsTaxableYN: product.ProductWithInventory.Product.IsTaxableYN,
          TaxCode: product.ProductWithInventory.Product.TaxCode,
          IsPriceInclusiveTaxYN:
            product.ProductWithInventory.Product.IsPriceInclusiveTaxYN,
          Status: product.ProductWithInventory.Product.Status,
          VerifiedYN: product.ProductWithInventory.Product.VerifiedYN,
          VerifiedById: product.ProductWithInventory.Product.VerifiedById,
          VerifiedAt: product.ProductWithInventory.Product.VerifiedAt,
          VariantLevels: product.ProductWithInventory.Product.VariantLevels,
          VariantTypeLevel1:
            product.ProductWithInventory.Product.VariantTypeLevel1,
          VariantTypeLevel2:
            product.ProductWithInventory.Product.VariantTypeLevel2,
          VariantTypeLevel3:
            product.ProductWithInventory.Product.VariantTypeLevel3,
          CreatedById: product.ProductWithInventory.Product.CreatedById,
          CreatedAt: product.ProductWithInventory.Product.CreatedAt,
          UpdatedById: product.ProductWithInventory.Product.UpdatedById,
          UpdatedAt: product.ProductWithInventory.Product.UpdatedAt,
          UpdatedSSYN: product.ProductWithInventory.Product.UpdatedSSYN,
          UOM: product.ProductWithInventory.UOM,
          TotalUnits: product.ProductWithInventory.TotalUnits,
          TotalUnitsAvailable: product.ProductWithInventory.TotalUnitsAvailable,
          TotalUnitsInCurrentOrder:
            product.ProductWithInventory.TotalUnitsInCurrentOrder,
          TotalUnitsSold: product.ProductWithInventory.TotalUnitsSold,
          TotalUnitsReserved: product.ProductWithInventory.TotalUnitsReserved,
          TotalUnitsOnConsignment:
            product.ProductWithInventory.TotalUnitsOnConsignment,
          TotalUnitsUnderMaintenance:
            product.ProductWithInventory.TotalUnitsUnderMaintenance,
          TotalUnitsVoid: product.ProductWithInventory.TotalUnitsVoid,
          TotalUnitsInTransit: product.ProductWithInventory.TotalUnitsInTransit,
          TotalUnitsBackOrdered:
            product.ProductWithInventory.TotalUnitsBackOrdered,
          TotalUnitsPreOrdered:
            product.ProductWithInventory.TotalUnitsPreOrdered,
          StockLowAlertLevel: product.ProductWithInventory.StockLowAlertLevel,
          StockReorderLevel: product.ProductWithInventory.StockReorderLevel,
          BufferStockLevel: product.ProductWithInventory.BufferStockLevel,
          ProfileId: product.ProfileId,
          Surface: product.Surface,
          Uniqueness: product.Uniqueness,
          HasCertificateYN: product.HasCertificateYN,
          PreviousSKU: product.ProductWithInventory.Product.PreviousSKU,
        };

        if (productAttr) {
          const productJewellery = new ProductJewellery(productAttr);
          return productJewellery;
        } else {
          throw Error('ProductId not found.');
        }
      } else {
        if (!productInfo) {
          throw Error('Product info is required.');
        }
        return new ProductJewellery(productInfo);
      }
    } catch (err) {
      throw Error('Failed to instantiate ProductJewellery.');
    }
  }

  async create(
    loginUser: LoginUser,
    dbTransaction: any,
    profileId?: string,
  ): Promise<any> {
    try {
      if (!profileId) {
        throw new Error('ProfileId is required.');
      }
      let stockProfile: StockProfile;
      try {
        stockProfile = await StockProfile.init(profileId);
      } catch (error) {
        if (error.message && error.message === 'ProfileId not found') {
          throw new RecordNotFoundError(
            'ProductJewelleryErrMsg01',
            'Stock Profile not found. Please create Stock Profile first.',
          );
        } else {
          throw error;
        }
      }
      this.ProfileId = stockProfile.ProfileId;
      const parentData = await super.create(loginUser, dbTransaction);
      const data = await ProductJewellery._ProductJewelleryRepository.create(
        {
          ProfileId: this._ProfileId,
          ProductId: parentData.ProductId,
          Surface: this._Surface,
          Uniqueness: this._Uniqueness,
          HasCertificateYN: this._HasCertificateYN,
        },
        {
          transaction: dbTransaction,
        },
      );

      const entityValueAfter = data.get({ plain: true });

      const activity = new Activity();
      activity.ActivityId = cuid();
      activity.Action = ActionEnum.CREATE;
      activity.Description = 'Add new product with inventory';
      activity.EntityType = 'ProductWithInventory';
      activity.EntityId = data.ProductId;
      activity.EntityValueBefore = JSON.stringify({});
      activity.EntityValueAfter = JSON.stringify(entityValueAfter);

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

  static async findAll(
    loginUser: LoginUser,
    dbTransaction?: any,
    searchOptions?: { page: any; rows: any; search: any },
  ): Promise<
    ProductJewelleryModel[] | { rows: ProductJewelleryModel[]; count: number }
  > {
    try {
      await super.findAll(loginUser);

      if (searchOptions.page && searchOptions.rows) {
        const offset = searchOptions.rows * (searchOptions.page - 1);

        let searchObj: any;
        try {
          searchObj = searchOptions.search
            ? JSON.parse(searchOptions.search)
            : {};
        } catch (err) {
          throw new Error('Bad value for search.');
        }

        const {
          StoreId,
          CategoryCode,
          CollectionCode,
          CertificateId,
          GroupCode,
          Brand,
          TagId,
          CustomizeOptionId,
          MaterialId,
          ...prodFilter
        } = searchObj;
        const productQueryObj = {};

        Object.entries(prodFilter).forEach(([key, value]) => {
          productQueryObj[key] = {
            [Op.substring]: value,
          };
        });

        return await ProductJewellery._ProductJewelleryRepository.findAllWithPagination(
          {
            productQuery: {
              whereProduct: productQueryObj,
              StoreId,
              CategoryCode,
              CollectionCode,
              CertificateId,
              GroupCode,
              Brand,
              TagId,
              CustomizeOptionId,
              MaterialId,
            },
            rows: searchOptions.rows,
            offset,
            transaction: dbTransaction,
          },
        );
      } else {
        return await ProductJewellery._ProductJewelleryRepository.findAll(
          dbTransaction,
        );
      }
    } catch (error) {
      throw error;
    }
  }

  public async update(
    loginUser: LoginUser,
    productInfo?: IJewelleryProductAttr,
    dbTransaction?: any,
  ): Promise<any> {
    try {
      const updatedInfo = await super.update(
        loginUser,
        productInfo,
        dbTransaction,
      );
      return updatedInfo;
    } catch (error) {
      throw error;
    }
  }

  public async deactivate(
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<void> {
    super.deactivate(loginUser, dbTransaction);
  }

  public async verify(
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<void> {
    try {
      await super.verify(loginUser, dbTransaction);
    } catch (error) {
      throw error;
    }
  }

  public async delete(
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<void> {
    try {
      await super.delete(loginUser, dbTransaction);
    } catch (error) {
      throw error;
    }
  }

  public async activate(
    loginUser: LoginUser,
    dbTransaction?: any,
  ): Promise<void> {
    try {
      await super.activate(loginUser, dbTransaction);
    } catch (error) {
      throw error;
    }
  }

  static async findOne(
    options: any,
    dbTransaction?: any,
  ): Promise<ProductJewelleryModel> {
    try {
      //check if option string
      const dbOptions =
        typeof options !== 'string'
          ? options
          : {
              where: {
                ProductId: options,
              },
              include: [
                {
                  model: ProductWithInventoryModel,
                  include: [
                    {
                      model: ProductModel,
                      include: [
                        {
                          model: SettingsCategoryModel,
                          attributes: ['Code', 'Name', 'Description', 'Status'],
                        },
                        {
                          model: SettingsCollectionModel,
                          attributes: ['Code', 'Name', 'Description', 'Status'],
                        },
                        {
                          model: SettingsGroupModel,
                          attributes: ['Code', 'Name', 'Description', 'Status'],
                        },
                        { model: ProductCertificateModel },
                        { model: ProductTagModel },
                        { model: ProductBrandModel },
                        { model: ProductCustomizeOptionModel },
                        { model: ProductMaterialModel },
                        {
                          model: SettingsGroupModel,
                        },
                      ],
                    },
                  ],
                },
                {
                  model: StockProfileModel,
                },
              ],
            };
      return await ProductJewellery._ProductJewelleryRepository.findOne({
        ...dbOptions,
        transaction: dbTransaction,
      });
    } catch (error) {
      throw error;
    }
  }

  public async addVariant(
    addVariantInfo: IProductVariantCreate,
    loginUser: LoginUser,
    dbTransaction?: any,
  ) {
    try {
      const product = await ProductJewellery._Repository.findOne({
        where: {
          SKU: addVariantInfo.SKU,
        },
        transaction: dbTransaction,
      });

      if (product) {
        throw new ClassError(
          'ProductVariant',
          'ProductVariantErrMsg03',
          'SKU must be unique.',
        );
      }

      const variant = await ProductVariantWithInventory.init();
      variant.ProductId = this.ProductId;
      variant.Name = addVariantInfo.Name;
      variant.Description = addVariantInfo.Description;
      variant.Type = addVariantInfo.Type;
      variant.Level = addVariantInfo.Level;
      variant.StockLowAlertLevel = addVariantInfo.StockLowAlertLevel;
      variant.StockReorderLevel = addVariantInfo.StockReorderLevel;
      variant.BufferStockLevel = addVariantInfo.BufferStockLevel;
      await variant.setSKU(addVariantInfo.SKU);
      await variant.setParentId(addVariantInfo.ParentId);
      await variant.setColour(addVariantInfo.Colour);
      await variant.setSize(addVariantInfo.Size);

      switch (variant.Type) {
        case ProductVariantType.HEIGHT:
          await variant.setMinMax(
            variant.Type,
            addVariantInfo.MinHeight,
            addVariantInfo.MaxHeight,
          );
          break;
        case ProductVariantType.LENGTH:
          await variant.setMinMax(
            variant.Type,
            addVariantInfo.MinLength,
            addVariantInfo.MaxLength,
          );
          break;
        case ProductVariantType.WIDTH:
          await variant.setMinMax(
            variant.Type,
            addVariantInfo.MinWidth,
            addVariantInfo.MaxWidth,
          );
          break;
        case ProductVariantType.WEIGHT:
          await variant.setMinMax(
            variant.Type,
            addVariantInfo.MinWeight,
            addVariantInfo.MaxWeight,
          );
          break;
        default:
          break;
      }

      await variant.create(loginUser, dbTransaction);

      return {
        VariantId: variant.VariantId,
        ProductId: variant.ProductId,
        Name: variant.Name,
        Description: variant.Description,
        SKU: variant.SKU,
        Size: variant.Size,
        Colour: variant.Colour,
        Type: variant.Type,
        Level: variant.Level,
        ParentId: variant.ParentId,
        MinWeight: variant.MinWeight,
        MaxWeight: variant.MaxWeight,
        MinWidth: variant.MinWidth,
        MaxWidth: variant.MaxWidth,
        MinHeight: variant.MinHeight,
        MaxHeight: variant.MaxHeight,
        MinLength: variant.MinLength,
        MaxLength: variant.MaxLength,
        Status: variant.Status,
        CreatedById: variant.CreatedById,
        CreatedAt: variant.CreatedAt,
        UpdatedById: variant.UpdatedById,
        UpdatedAt: variant.UpdatedAt,
        UpdatedSSYN: variant.UpdatedSSYN,
        TotalUnitsAvailable: variant.TotalUnitsAvailable,
        TotalUnitsInCurrentOrder: variant.TotalUnitsInCurrentOrder,
        TotalUnitsSold: variant.TotalUnitsSold,
        TotalUnitsReserved: variant.TotalUnitsReserved,
        TotalUnitsInTransit: variant.TotalUnitsInTransit,
        TotalUnitsOnConsignment: variant.TotalUnitsOnConsignment,
        TotalUnitsBackOrdered: variant.TotalUnitsBackOrdered,
        TotalUnitsPreOrdered: variant.TotalUnitsPreOrdered,
        StockLowAlertLevel: variant.StockLowAlertLevel,
        StockReorderLevel: variant.StockReorderLevel,
        BufferStockLevel: variant.BufferStockLevel,
      };
    } catch (error) {
      throw error;
    }
  }
}
