import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ErrorService } from '../../error/error.service';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
} from '../../utils';
import { In, QueryRunner, Repository } from 'typeorm';
import {
  ProductEntity,
  ProductWaitingOperation,
} from './entities/product.entity';
import { ProductMakerBrandDto } from './dto/product-maker-brand.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { ProductMakerBrandMappingEntity } from './entities/product-maker-brand-mapping.entity';
import { ProductAttributeMappingEntity } from './entities/product-attribute-mapping.entity';
import { CreateProductDto } from './dto/create-product.dto';
import { ProductPictureMappingEntity } from './entities/product-picture-mapping.entity';
import { PictureEntity } from './entities/picture.entity';
import { CreateProductPictureDto } from './dto/create-product-picture.dto';
import { QuantityUnitEntity } from './entities/quantity-unit.entity';
import { CreateQuantityUnitDto } from './dto/create-quantity-unit.dto';
import { UpdateQuantityUnitDto } from './dto/update-quantity-unit.dto';
import {
  ProductNotifQuantityEntity,
  ProductNotifQuantityStatus,
} from './entities/product-notif-quantity.entity';
import { CreateNotifListDto } from './dto/create-notif-list.dto';
import { SmsService } from 'src/sms/sms.service';
import { MakerBrandProductDto } from '../car/dto/maker-brand-product.dto';
import { CreateLinkAttributeToProductDto } from './dto/create-link-attribute-to-product.dto';
import { getLastDisplayOrder } from 'src/utils/last-display-order';
import { SiteInfoService } from '../siteInfo/site-info.service';
import { LogService } from '../log/log.service';
import { LogAction, LogType } from '../log/log.interface';
import {
  OrderPaymentStatus,
  OrderStatus,
} from '../order/entities/order.entity';

@Injectable()
export class ProductService {
  constructor(
    @InjectRepository(ProductEntity)
    private productRepository: Repository<ProductEntity>,

    @InjectRepository(ProductMakerBrandMappingEntity)
    private makerBrandMappingRepository: Repository<ProductMakerBrandMappingEntity>,

    @InjectRepository(ProductAttributeMappingEntity)
    private attributeMappingRepository: Repository<ProductAttributeMappingEntity>,

    @InjectRepository(PictureEntity)
    private pictureRepository: Repository<PictureEntity>,

    @InjectRepository(ProductPictureMappingEntity)
    private productPictureMappingRepository: Repository<ProductPictureMappingEntity>,

    @InjectRepository(QuantityUnitEntity)
    private quantityUnitRepository: Repository<QuantityUnitEntity>,

    @InjectRepository(ProductNotifQuantityEntity)
    private productNotifQuantityRepository: Repository<ProductNotifQuantityEntity>,

    private error: ErrorService,
    private sms: SmsService,
    private siteInfoService: SiteInfoService,
    private logService: LogService,
  ) {}

  /**
   * -------------------------------------------------------
   * GET /products/waitings/admin
   */
  async waitingList(page = 1, limit = 20, filters = null, sorts = null) {
    let builder = this.productRepository
      .createQueryBuilder('product')
      .innerJoinAndSelect('product.category', 'category')
      .innerJoin('product.creatorUser', 'creatorUser')
      .addSelect([
        'creatorUser.id',
        'creatorUser.name',
        'creatorUser.surName',
        'creatorUser.mobile',
      ])
      .andWhere('product.isWaitingOperation IS NOT NULL')
      .take(limit)
      .skip((page - 1) * limit);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('product.updatedAt', 'ASC');
    }

    builder = applyFiltersToBuilder(builder, filters);

    const [items, totalItems] = await builder.getManyAndCount();

    return {
      items,
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getAll(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    makerBrandId = null,
    hasNotifQuantity = false,
    makerBrandIdForMapping = null,
    wage = null,
    userId: number,
    accessAll = false,
  ) {
    let builder = this.productRepository
      .createQueryBuilder('product')
      .innerJoinAndSelect('product.category', 'category')
      .innerJoin('product.creatorUser', 'creatorUser')
      .addSelect([
        'creatorUser.id',
        'creatorUser.name',
        'creatorUser.surName',
        'creatorUser.mobile',
      ])
      .addSelect(
        `SUM(IF(notifQuantities.status = '${ProductNotifQuantityStatus.pending}', 1, 0))`,
        'countNotifQuantity',
      )
      .groupBy('product.id')
      .take(limit)
      .skip((page - 1) * limit);

    if (hasNotifQuantity) {
      builder.innerJoin(
        'product.productNotifQuantities',
        'notifQuantities',
        'notifQuantities.status = :pending',
        { pending: ProductNotifQuantityStatus.pending },
      );
    } else {
      builder.leftJoin('product.productNotifQuantities', 'notifQuantities');
    }

    // For getting wage price in service
    if (makerBrandId) {
      builder.innerJoinAndSelect(
        'product.makerBrandMappings',
        'makerBrandMappings',
        'makerBrandMappings.makerBrandId = :makerBrandId',
        { makerBrandId },
      );
    }

    if (makerBrandIdForMapping) {
      builder.leftJoin(
        'product.productMappings',
        'productMappings',
        'productMappings.makerBrandId = :makerBrandId',
        { makerBrandId: makerBrandIdForMapping },
      );
      builder.addSelect('productMappings.wagePrice');
      if (wage) builder.andWhere('productMappings.wagePrice = :wage', { wage });
    }

    if (!accessAll) {
      builder.andWhere('product.creatorUserId = :userId', { userId });
    }

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('product.displayOrder', 'ASC');
    }

    builder = applyFiltersToBuilder(builder, filters);

    const [items, totalItems] = await builder.getManyAndCount();

    return {
      items,
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getAllForMapping(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    makerBrandIdForMapping = null,
    wage = null,
  ) {
    let builder = this.productRepository
      .createQueryBuilder('product')
      .innerJoinAndSelect('product.category', 'category')
      .andWhere({ deleted: false })
      .andWhere({ published: true })
      .select([
        'product.id',
        'product.productId',
        'product.image',
        'product.name',
        'product.createdAt',

        'category.id',
        'category.name',
      ])
      .take(limit)
      .skip((page - 1) * limit);

    if (makerBrandIdForMapping) {
      builder.leftJoinAndSelect(
        'product.makerBrandMappings',
        'makerBrandMappings',
        'makerBrandMappings.makerBrandId = :makerBrandId',
        { makerBrandId: makerBrandIdForMapping },
      );
      if (wage) {
        builder.andWhere('makerBrandMappings.wagePrice = :wage', { wage });
      }
    }

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    }

    builder = applyFiltersToBuilder(builder, filters);

    const [items, totalItems] = await builder.getManyAndCount();

    return {
      items,
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getFull(userId, onlyCreator = false) {
    const where: any = { deleted: false };

    if (onlyCreator) {
      where.creatorUserId = userId;
    }

    return await this.productRepository.find({
      select: ['id', 'productId', 'name', 'categoryId'],
      where,
    });
  }

  /**
   * -------------------------------------------------------
   */
  async makerBrandMapping(
    productId: string,
    dto: ProductMakerBrandDto,
    operatorUserId: string,
    accessAll = false,
  ) {
    const { name: productName, creatorUserId } =
      await this.productRepository.findOne({
        where: { id: productId },
        select: ['name', 'creatorUserId'],
      });
    if (creatorUserId !== operatorUserId && !accessAll) {
      this.error.methodNotAllowed([
        'شما تنها اجازه اختصاص محصولات خود را دارید!',
      ]);
    }

    await this.makerBrandMappingRepository.delete({ productId });

    await this.makerBrandMappingRepository
      .createQueryBuilder()
      .insert()
      .values(
        dto.mappings.map((mapping) => ({
          ...mapping,
          productId,
          createdAt: new Date(),
          updatedAt: new Date(),
        })),
      )
      .execute();

    // log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.update,
      operatorUserId,
      message: `محصول ${productName} به برندهای خودروی موردنظر، تخصیص داده شد.`,
      affectedId: productId,
    });
    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async makerBrandToProductMapping(
    makerBrandId: number,
    dto: MakerBrandProductDto,
  ) {
    await this.makerBrandMappingRepository.delete({ makerBrandId });

    await this.makerBrandMappingRepository
      .createQueryBuilder()
      .insert()
      .values(
        dto.mappings.map((mapping) => ({
          ...mapping,
          makerBrandId,
          createdAt: new Date(),
          updatedAt: new Date(),
        })),
      )
      .execute();

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async getById(id: string) {
    return await this.productRepository
      .createQueryBuilder('product')

      .leftJoinAndSelect('product.manufacturer', 'manufacturer')
      .leftJoinAndSelect('product.category', 'category')

      // .leftJoinAndSelect('product.makerBrandMappings', 'makerBrandMappings')
      // .leftJoinAndSelect('makerBrandMappings.makerBrand', 'makerBrand')

      // .leftJoinAndSelect('product.attributeMappings', 'attributeMappings')
      // .leftJoinAndSelect('attributeMappings.attributeOption', 'attributeOption')
      // .leftJoinAndSelect('attributeOption.attribute', 'attribute')

      .andWhere({ id })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async getRawById(id: string) {
    return await this.productRepository.findOne(id);
  }

  /**
   * -------------------------------------------------------
   */
  async getAllRawByIds(ids: string[]) {
    return await this.productRepository.find({ where: { id: In(ids) } });
  }

  /**
   * -------------------------------------------------------
   * PUT /products/:id/waiting/admin
   */
  async confirmWaiting(id: string) {
    const product = await this.productRepository.findOne(id);

    if (product.isWaitingOperation === null) {
      this.error.unprocessableEntity([
        'محصول موردنظر قبلا تعیین وضعیت شده است',
      ]);
    }

    if (product.isWaitingOperation === ProductWaitingOperation.delete) {
      product.deleted = true;
    }

    if (product.isWaitingOperation === ProductWaitingOperation.publish) {
      product.published = true;
    }

    if (product.isWaitingOperation === ProductWaitingOperation.un_publish) {
      product.published = false;
    }

    if (product.isWaitingOperation === ProductWaitingOperation.add) {
      product.published = true;
    }

    product.isWaitingOperation = null;
    product.updatedAt = new Date();
    await product.save();

    // Send SMS to creator

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async updateById(
    productId: string,
    dto: UpdateProductDto,
    image: Express.Multer.File,
    operatorUserId: string,
    accessAll = false,
  ) {
    const oldProduct = await this.productRepository.findOne(productId);
    if (oldProduct?.creatorUserId !== operatorUserId && !accessAll) {
      this.error.methodNotAllowed([
        'شما تنها اجازه ویرایش محصولات خود را دارید!',
      ]);
    }

    if (image) {
      dto.image = `/uploads/products/${image.filename}`;
    }

    if (!dto.weight) dto.weight = null;
    if (!dto.length) dto.length = null;
    if (!dto.width) dto.width = null;
    if (!dto.height) dto.height = null;

    const extraValue: any = {};
    if (!accessAll) {
      if (oldProduct.published && !dto.published) {
        extraValue.isWaitingOperation = ProductWaitingOperation.un_publish;
        dto.published = true;
      } else if (!oldProduct.published && dto.published) {
        extraValue.isWaitingOperation = ProductWaitingOperation.publish;
        dto.published = false;
      }
    }

    const data = await this.productRepository
      .createQueryBuilder()
      .update()
      .set({ ...dto, ...extraValue, updatedAt: new Date() })
      .where({ id: productId })
      .execute();

    // log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.update,
      operatorUserId,
      message: `محصول ${oldProduct.name} ویرایش شد.`,
      affectedId: productId,
      item: dto,
      oldItem: oldProduct,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async statistic() {
    const published = await this.productRepository.count({
      published: true,
      deleted: false,
    });

    return {
      published,
    };
  }

  /**
   * -------------------------------------------------------
   * show sales of products
   * GET /products/statistic/sales/admin
   */
  async statisticSales(filters = null, userId: number, accessAll = false) {
    let builder = this.productRepository
      .createQueryBuilder('product')
      .groupBy('product.id')
      .innerJoin('product.orderItems', 'orderItems')
      .innerJoin('orderItems.order', 'order')
      .leftJoin('product.quantityUnit', 'quantityUnit')
      .select([
        'product.id AS id',
        'product.productId AS productId',
        'product.name AS name',
        'product.image AS image',
        'quantityUnit.name AS unit',
        'SUM(orderItems.price * orderItems.quantity) AS totalAmount',
        'SUM(orderItems.quantity) AS totalQuantity',
      ])
      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.paid,
      })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .orderBy('totalQuantity', 'DESC');

    builder = applyFiltersToBuilder(builder, filters);

    if (!accessAll) {
      builder.andWhere('product.creatorUserId = :userId', { userId });
    }

    const list = await builder.getRawMany();
    return list.map((item) => ({
      ...item,
      totalAmount: +item.totalAmount,
      totalQuantity: +item.totalQuantity,
    }));
  }
  /**
   * -------------------------------------------------------
   */
  async calTotalPrice(products, isService = false, makerBrandId = null) {
    let orderTotal = 0;
    let wageTotal = 0;
    let serviceDiscount = 0;
    let orderTax = 0;
    const categories = [];

    // const { percentVAT } = await this.siteInfoService.getInfo(['percentVAT']);

    for (let i = 0; i < products.length; i++) {
      const product = products[i];

      let calWage = false;
      if (!categories.find((c) => c === product.categoryId)) {
        calWage = true;
        categories.push(product.categoryId);
      }

      const { itemPrice, wage, tax, discount } = await this.calPriceAndWage(
        product,
        calWage,
        isService,
        makerBrandId,
      );
      product.wagePrice = wage;
      product.tax = tax;

      orderTotal += itemPrice;
      wageTotal += wage;
      orderTax += tax;
      serviceDiscount += discount;
    }

    return { orderTotal, wageTotal, orderTax, serviceDiscount };
  }

  /**
   * -------------------------------------------------------
   */
  async calPriceAndWage(
    product,
    calWage = false,
    isService = false,
    makerBrandId = null,
  ) {
    let itemPrice = 0;
    let wage = 0;
    let discount = 0;
    const tax = 0;

    if (
      isService &&
      product.priceInService > 0 &&
      product.price > product.priceInService
    ) {
      discount = (product.price - product.priceInService) * product.quantity;
    }

    itemPrice = product.price * product.quantity;

    if (isService && calWage) {
      wage = await this._getWageProduct(product.id, makerBrandId);
    }

    // if (product.taxCategoryId) {
    //   const percent = await getTaxPercent(product.taxCategoryId);
    //   tax += (product.price * percent) / 100;
    // }

    return { itemPrice, wage, tax, discount };
  }

  /**
   * -------------------------------------------------------
   */
  private async _getWageProduct(productId: string, makerBrandId: number) {
    const makerBrandMapping = await this.makerBrandMappingRepository.findOne({
      where: { productId, makerBrandId },
      select: ['wagePrice'],
    });
    return makerBrandMapping?.wagePrice || 0;
  }

  /**
   * -------------------------------------------------------
   * Add product
   */
  async addProduct(
    dto: CreateProductDto,
    image: Express.Multer.File,
    operatorUserId: string,
    accessAll = false,
  ) {
    const data = new ProductEntity();
    for (const key in dto) {
      data[key] = dto[key];
    }

    // Getting the last product ID
    const { productId } = await this.productRepository.findOne({
      order: { productId: 'DESC' },
      select: ['productId'],
    });

    data.creatorUserId = operatorUserId;
    data.image = `/uploads/products/${image.filename}`;
    data.displayOrder = await getLastDisplayOrder(ProductEntity.name);

    data.createdAt = new Date();
    data.updatedAt = new Date();
    data.productId = productId + 1;

    if (!data.weight) data.weight = null;
    if (!data.length) data.length = null;
    if (!data.width) data.width = null;
    if (!data.height) data.height = null;

    if (!accessAll) {
      data.published = false;
      data.isWaitingOperation = ProductWaitingOperation.add;
    }

    const { identifiers } = await this.productRepository
      .createQueryBuilder()
      .insert()
      .values(data)
      .execute();

    const newProductId = identifiers[0].id;
    const newProduct = await this.productRepository.findOne(newProductId);

    // Adding a log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.insert,
      operatorUserId,
      message: `محصول ${newProduct.name} ایجاد شد`,
      affectedId: newProductId,
      item: newProduct,
    });

    return newProduct;
  }

  /**
   * -------------------------------------------------------
   * Add product pictures
   */
  async addProductPicture(
    dto: CreateProductPictureDto,
    image: Express.Multer.File,
    operatorUserId: string,
  ) {
    const product = await this.productRepository.findOne(dto.productId);

    const { identifiers } = await this.pictureRepository
      .createQueryBuilder()
      .insert()
      .values({
        filename: image.filename,
        path: `/uploads/products/${image.filename}`,
        isMap: false,
        createdAt: new Date(),
        updatedAt: new Date(),
      })
      .execute();

    const newPictureId = identifiers[0].id;

    await this.productPictureMappingRepository
      .createQueryBuilder()
      .insert()
      .values({
        createdAt: new Date(),
        updatedAt: new Date(),
        displayOrder: await getLastDisplayOrder(
          ProductPictureMappingEntity.name,
        ),
        productId: dto.productId,
        pictureId: newPictureId,
      })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.insert,
      operatorUserId,
      message: `تصویر جدید، به محصول ${product.name} اضافه شد.`,
      affectedId: dto.productId,
      item: { picture: `/uploads/products/${image.filename}` },
    });

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async getPictureByProductId(productId: string) {
    return await this.productPictureMappingRepository
      .createQueryBuilder('mapping')
      .innerJoinAndSelect('mapping.picture', 'picture')
      .andWhere({ productId })
      .getMany();
  }

  /**
   * -------------------------------------------------------
   * Delete product pictures
   */
  async deleteProductPicture(
    pictureId: number,
    productId: string,
    operatorUserId: string,
  ) {
    const product = await this.productRepository.findOne(productId);

    await this.productPictureMappingRepository.delete({ pictureId, productId });

    await this.pictureRepository.delete(pictureId);

    // Adding a log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.delete,
      operatorUserId,
      message: `تصویر مربوطه از محصول ${product.name} حذف گردید.`,
      affectedId: productId,
    });

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async getQuantityUnits() {
    return await this.quantityUnitRepository.find();
  }

  /**
   * -------------------------------------------------------
   */
  async getQuantityUnitById(id: number) {
    return await this.quantityUnitRepository
      .createQueryBuilder('units')
      .andWhere({ id })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async addQuantityUnit(dto: CreateQuantityUnitDto, operatorUserId: string) {
    const newUnit = new QuantityUnitEntity();
    for (const key in dto) {
      newUnit[key] = dto[key];
    }

    newUnit.displayOrder = await getLastDisplayOrder(QuantityUnitEntity.name);
    newUnit.createdAt = new Date();
    newUnit.updatedAt = new Date();

    const { identifiers } = await this.quantityUnitRepository
      .createQueryBuilder()
      .insert()
      .values(newUnit)
      .execute();

    const newUnitId = identifiers[0].id;
    const data = await this.quantityUnitRepository.findOne(newUnitId);

    // Adding a log
    await this.logService.add({
      type: LogType.quantity_unit,
      action: LogAction.insert,
      operatorUserId,
      message: `واحد اندازه گیری ${newUnit} اضافه شد.`,
      affectedId: newUnitId,
      item: newUnit,
    });
    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async updateQuantityUnit(
    unitId: number,
    dto: UpdateQuantityUnitDto,
    operatorUserId: string,
  ) {
    const unit = await this.quantityUnitRepository.findOne(unitId);

    const data = await this.quantityUnitRepository
      .createQueryBuilder()
      .update()
      .set({ ...dto, updatedAt: new Date() })
      .where({ id: unitId })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.quantity_unit,
      action: LogAction.update,
      operatorUserId,
      message: `واحد اندازه گیری ${unit} ویرایش شد.`,
      affectedId: String(unitId),
      item: unit,
    });
    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async deleteQuantityUnit(id: number) {
    try {
      return await this.quantityUnitRepository.delete(id);
    } catch (err) {
      console.log(err);
      this.error.internalServerError([
        'امکان حذف این واحد اندازه گیری، به دلیل ارتباط با یک محصول، وجود ندارد',
      ]);
    }
  }

  // /**
  //  * -------------------------------------------------------
  //  */
  // async getCountAttributeMapping(attributeId: number) {
  //   return await this.attributeMappingRepository.count({
  //     specificationAttributeOptionId: attributeId,
  //   });
  // }

  /**
   * -------------------------------------------------------
   */
  async getPendingNotifCountByProductId(productId: string) {
    return await this.productNotifQuantityRepository.count({
      productId,
      status: ProductNotifQuantityStatus.pending,
    });
  }

  /**
   * -------------------------------------------------------
   */
  async sendNotifForList(dto: CreateNotifListDto, operatorUserId: string) {
    const product = await this.productRepository.findOne(dto.productId);

    const list = await this.productNotifQuantityRepository.find({
      where: {
        productId: dto.productId,
        status: ProductNotifQuantityStatus.pending,
      },
      relations: ['product', 'user'],
    });

    for (let i = 0; i < list.length; i++) {
      const item = list[i];

      this.sms
        .sendNotifyProductQuantity(
          item.user.mobile,
          item.product.name,
          item.product.productId,
        )
        .then((msg) => console.log({ msg }))
        .catch((err) => console.log({ err }));
    }

    await this.productNotifQuantityRepository
      .createQueryBuilder()
      .update()
      .set({
        status: ProductNotifQuantityStatus.done,
        updatedAt: new Date(),
      })
      .where({
        productId: dto.productId,
        status: ProductNotifQuantityStatus.pending,
      })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.insert,
      operatorUserId,
      message: `شارژ موجودی محصول ${product.name} اطلاع رسانی شد.`,
      affectedId: dto.productId,
      item: product,
    });
    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async linkAttributeToProduct(
    dto: CreateLinkAttributeToProductDto,
    operatorUserId: string,
  ) {
    const product = await this.productRepository.findOne(dto.productId);

    const newLink = new ProductAttributeMappingEntity();

    newLink.specificationAttributeOptionId = dto.optionId;
    newLink.productId = dto.productId;
    newLink.displayOrder = await getLastDisplayOrder(
      ProductAttributeMappingEntity.name,
    );

    newLink.createdAt = new Date();
    newLink.updatedAt = new Date();

    const { identifiers } = await this.attributeMappingRepository
      .createQueryBuilder()
      .insert()
      .values(newLink)
      .execute();

    const newLinkId = identifiers[0].id;
    const data = await this.attributeMappingRepository.findOne(newLinkId);

    // log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.insert,
      operatorUserId,
      message: `ویژگی موردنظر به محصول ${product.name} اضافه شد.`,
      affectedId: dto.productId,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async unlinkAttributeToProduct(
    productId: string,
    optionId: number,
    operatorUserId: string,
  ) {
    const product = await this.productRepository.findOne(productId);

    const data = await this.attributeMappingRepository.delete({
      productId,
      specificationAttributeOptionId: optionId,
    });

    // log
    await this.logService.add({
      type: LogType.product,
      action: LogAction.delete,
      operatorUserId,
      message: `ویژگی موردنظر از محصول ${product.name} حذف شد.`,
      affectedId: productId,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async updateStockQuantity(
    id: string,
    increaseDescreaseQuantity: number,
    isOilGovernmentOrder = false,
    queryRunner: QueryRunner = null,
  ) {
    const product = await this.productRepository.findOne(id);
    const isOil = product.categoryId === 1;

    let sendAlertSms = false;

    if (isOilGovernmentOrder && isOil) {
      product.governmentOilStockQuantity += increaseDescreaseQuantity;
      sendAlertSms =
        product.governmentOilStockQuantity <=
        product.notifyAdminForQuantityBelow;
    } else {
      product.stockQuantity += increaseDescreaseQuantity;
      sendAlertSms =
        product.stockQuantity <= product.notifyAdminForQuantityBelow;
    }

    if (queryRunner) {
      await queryRunner.manager.save(product);
    } else {
      await product.save();
    }

    if (sendAlertSms) {
      const { adminMobile } = await this.siteInfoService.getInfo([
        'adminMobile',
      ]);

      await this.sms.notifyAdminForQuantityBelow(
        adminMobile,
        product.productId,
      );
    }
  }

  /**
   * -------------------------------------------------------
   */
  async deleteProduct(id: number, operatorUserId: string, accessAll = false) {
    const product = await this.productRepository.findOne(id);

    if (product?.creatorUserId !== operatorUserId && !accessAll) {
      this.error.methodNotAllowed(['شما تنها اجازه حذف محصولات خود را دارید!']);
    }

    if (accessAll) {
      product.deleted = true;
    } else {
      product.published = false;
      product.isWaitingOperation = ProductWaitingOperation.delete;
    }

    product.updatedAt = new Date();
    await product.save();

    // Adding a log
    if (accessAll) {
      await this.logService.add({
        type: LogType.product,
        action: LogAction.delete,
        operatorUserId,
        message: `محصول ${product.name} حذف شد`,
        affectedId: product.id,
        item: product,
      });
    }

    return true;
  }
}
