import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Between, In, LessThan, MoreThan, Not, Repository } from 'typeorm';
import {
  OrderClientType,
  OrderEntity,
  OrderPaymentGateway,
  OrderPaymentMethod,
  OrderPaymentStatus,
  OrderShippingType,
  OrderStatus,
  ShippingName,
} from './entities/order.entity';

import * as moment from 'jalali-moment';
import { v4 as uuidv4 } from 'uuid';
import { AddOrderDto } from './dto/add-order.dto';
import { ProductService } from '../product/product.service';
import { SiteInfoService } from '../siteInfo/site-info.service';
import { OrderItemEntity } from './entities/order-item.entity';
import { AddServiceDto } from '../service/dto/add-service.dto';
import { UpdateOrderStatusDto } from './dto/update-order-status.dto';
import { PointService } from '../point/point.service';
import { SmsService } from '../../sms/sms.service';
import { CarService } from '../car/car.service';
import { PDF } from 'src/utils/pdf';
import { UpdateOrderServiceManDto } from './dto/update-order-service-man.dto';
import { UserService } from '../user/user.service';
import { ErrorService } from 'src/error/error.service';
import { AddressService } from '../address/address.service';
import { CityIds } from '../address/entities/city.entity';
import { AddProductDto } from './dto/add-product.dto';
import { DiscountService } from '../discount/discount.service';
import { UpdateOrderItemDto } from './dto/update-order-item.dto';
import { CalculatePriceForClientDto } from './dto/calculate-price-for-client.dto';
import { AlopeykService } from 'src/thirdParty/alopeyk/alopeyk.service';
import { requestShippingDto } from './dto/request-shipping.dto';
import { LogService } from '../log/log.service';
import { LogAction, LogType } from '../log/log.interface';
import { LogClient } from '../log/entities/log.entity';
import { MahexService } from '../../thirdParty/mahex/mahex.service';
import { SamtService } from '../../thirdParty/samt/samt.service';
import { UserEntity } from '../user/entities/user.entity';
import { GovernmentOilSaleOrderDto } from './dto/government-oil-sale-order.dto';
import { Workbook } from 'exceljs';
import * as tmp from 'tmp';
import { AddOrderByUserDto } from './dto/add-order-by-user.dto';
import { PayOrderByUserDto } from './dto/pay-order-by-user.dto';
import { PaymentService } from '../payment/payment.service';
import {
  PaymentGateway,
  PaymentStatus,
  PaymentType,
} from '../payment/entities/payment.entity';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  digit,
  encryptId,
  paginationResult,
} from '../../utils';
import { SnappPayService } from '../../gateway/snappPay/snappPay.service';
import { Transaction } from '../../utils/transaction';
import { SshafDto } from './dto/sshaf.dto';
import { SshafService } from '../../thirdParty/sshaf/sshaf.service';
import { SamtDto } from './dto/samt.dto';

@Injectable()
export class OrderService {
  constructor(
    @InjectRepository(OrderEntity)
    private orderRepository: Repository<OrderEntity>,
    @InjectRepository(OrderItemEntity)
    private orderItemRepository: Repository<OrderItemEntity>,
    private sms: SmsService,
    private productService: ProductService,
    private siteInfoService: SiteInfoService,
    private pointService: PointService,
    private addressService: AddressService,
    private discountService: DiscountService,
    private alopeykService: AlopeykService,
    private mahexService: MahexService,
    private samtService: SamtService,
    private error: ErrorService,
    private logService: LogService,
    private snappPayService: SnappPayService,
    private sshafService: SshafService,

    @Inject(forwardRef(() => CarService))
    private readonly carService: CarService,
    @Inject(forwardRef(() => UserService))
    private readonly userService: UserService,
    @Inject(forwardRef(() => PaymentService))
    private readonly paymentService: PaymentService,
  ) {}

  /**
   * -------------------------------------------------------
   * Admin
   * add product into order items
   */
  async addProduct(dto: AddProductDto) {
    const order = await this.orderRepository.findOne({
      where: { id: dto.orderId },
      relations: ['car', 'orderItems'],
    });

    if (order.paymentStatusCode === OrderPaymentStatus.paid) {
      this.error.methodNotAllowed([
        'اجازه تغییر در فاکتور پرداخت شده وجود ندارد',
      ]);
    }

    if (
      order.paymentStatusCode === OrderPaymentStatus.unsettledPaid &&
      order.snappPayPaymentToken
    ) {
      this.error.methodNotAllowed([
        'تنها امکان کاهش تعداد اقلام فاکتورهایی که از طریق اسنپ پی پرداخت شده اند، وجود دارد!',
      ]);
    }

    const isOilGovernment = order.governmentOilTrackingCode !== null;

    const {
      orderItems,
      orderTotal,
      wageTotal,
      vat,
      orderShipping,
      deductionsAmount,
      finalAmount,
      orderDiscount,
      couponDiscount,
      orderTax,
    } = await this.calculateFinalAmount({
      ...order,
      productItems: [...order.orderItems, { ...dto, isNew: true }], // merge new with old order items
      makerBrandId: order?.car?.makerBrandId || null,
      isNewOrder: false,
      isOilGovernment,
    });

    // Inserting into order item
    const { identifiers } = await this.orderItemRepository
      .createQueryBuilder()
      .insert()
      .values({
        orderId: order.id,
        productId: dto.productId,
        quantity: dto.quantity,
        price: orderItems[orderItems.length - 1].price,
        wagePrice: orderItems[orderItems.length - 1].wagePrice,
        tax: orderItems[orderItems.length - 1].tax,
        discount: 0,
        createdAt: new Date(),
        updatedAt: new Date(),
      })
      .execute();
    const orderItemId = identifiers[0].id;

    // Updating order
    await this.orderRepository
      .createQueryBuilder()
      .update()
      .set({
        orderTotal,
        wageTotal,
        vat,
        orderTax,
        couponDiscount,
        orderShipping,
        orderDiscount,
        deductionsAmount,
        finalAmount,
        updatedAt: new Date(),
      })
      .where({ id: dto.orderId })
      .execute();

    // Updating the product stock quantity
    await this.productService.updateStockQuantity(
      dto.productId,
      -1 * dto.quantity,
      isOilGovernment,
    );

    // Finding the new order item and returing it
    return await this.orderItemRepository.findOne(orderItemId);
  }

  /**
   * -------------------------------------------------------
   * Admin
   * update order item ( plus or minus product)
   */
  async updateOrderItem(dto: UpdateOrderItemDto) {
    const orderItem = await this.orderItemRepository.findOne({
      where: { id: dto.orderItemId },
      relations: ['product', 'order', 'order.orderItems', 'order.car'],
    });

    if (orderItem.order.paymentStatusCode === OrderPaymentStatus.paid) {
      this.error.methodNotAllowed([
        'اجازه تغییر در فاکتور پرداخت شده وجود ندارد',
      ]);
    }

    const isOilGovernment = orderItem.order.governmentOilTrackingCode !== null;

    const newQuantity = orderItem.quantity + dto.changeQuantity;

    const calItems: any = [...orderItem.order.orderItems];
    // Increasing
    if (dto.changeQuantity > 0) {
      calItems.push({
        productId: orderItem.productId,
        quantity: dto.changeQuantity,
        isNew: true,
      });
    }
    // Decreasing
    else {
      const index = calItems.findIndex((item) => item.id === orderItem.id);
      calItems[index].quantity = newQuantity;
    }

    // Calculating
    const {
      orderItems,
      orderTotal,
      wageTotal,
      vat,
      orderShipping,
      deductionsAmount,
      finalAmount,
      orderDiscount,
      couponDiscount,
      orderTax,
    } = await this.calculateFinalAmount({
      ...orderItem.order,
      productItems: calItems,
      makerBrandId: orderItem.order?.car?.makerBrandId || null,
      isNewOrder: false,
      isOilGovernment,
    });

    if (
      orderItem.order.paymentStatusCode === OrderPaymentStatus.unsettledPaid &&
      orderItem.order.snappPayPaymentToken &&
      orderItem.order.finalAmount < finalAmount
    ) {
      this.error.methodNotAllowed([
        'بدلیل اینکه این فاکتور از طریق اسنپ پی پرداخت شده است، باید مبلغ نهایی جدید کمتر از مبلغ قبل از ویرایش فاکتور باشد',
      ]);
    }

    // Using SQL transaction for rollback
    const transaction = new Transaction();
    const queryRunner = await transaction.start();

    try {
      // Inserting new record
      let newOrderItemId;
      if (
        dto.changeQuantity > 0 &&
        orderItem.product.price !== orderItem.price
      ) {
        const { identifiers } = await this.orderItemRepository
          .createQueryBuilder('orderItem', queryRunner)
          .insert()
          .values({
            orderId: orderItem.orderId,
            productId: orderItem.productId,
            price: orderItems[orderItems.length - 1].price,
            wagePrice: orderItems[orderItems.length - 1].wagePrice,
            tax: orderItems[orderItems.length - 1].tax,
            discount: 0,
            quantity: newQuantity,
            createdAt: new Date(),
            updatedAt: new Date(),
          })
          .execute();
        newOrderItemId = identifiers[0].id;
      }
      // Updating current record
      else {
        const found = orderItems.find(
          (item) => item.orderItemId === orderItem.id,
        );

        orderItem.quantity = newQuantity;
        orderItem.price = orderItem.price;
        orderItem.wagePrice = found?.wagePrice || orderItem.wagePrice;
        orderItem.tax = found?.tax || orderItem.tax;
        orderItem.updatedAt = new Date();
        await queryRunner.manager.save(orderItem);
      }

      // Updating the last order item with wage price
      if (newOrderItemId) {
        await this.orderItemRepository
          .createQueryBuilder('orderItem', queryRunner)
          .update()
          .set({ wagePrice: orderItems[orderItems.length - 1]?.wagePrice || 0 })
          .where({ id: newOrderItemId })
          .execute();
      }

      // Updating order
      await this.orderRepository
        .createQueryBuilder('order', queryRunner)
        .update()
        .set({
          orderTotal,
          wageTotal,
          vat,
          orderTax,
          couponDiscount,
          orderShipping,
          orderDiscount,
          deductionsAmount,
          finalAmount,
          updatedAt: new Date(),
        })
        .where({ id: orderItem.orderId })
        .execute();

      // Updating the product stock quantity
      await this.productService.updateStockQuantity(
        orderItem.productId,
        -1 * dto.changeQuantity,
        isOilGovernment,
        queryRunner,
      );

      await transaction.commit();

      // update request to snapp pay
      if (
        orderItem.order.paymentStatusCode ===
          OrderPaymentStatus.unsettledPaid &&
        orderItem.order.snappPayPaymentToken
      ) {
        const data = await this.prepareSnappPayCartList(orderItem.orderId);
        await this.snappPayService.updateRequest(
          orderItem.order.snappPayPaymentToken,
          data.cartList,
          data.finalAmount,
          data.discountAmount,
          data.shippingAmount,
          data.externalSourceAmount,
        );
      }
    } catch (e) {
      console.log(e);
      await transaction.rollback();
      this.error.internalServerError([
        'در ویرایش فاکتور خطایی رخ داده است با پشتیبانی تماس حاصل نمایید!',
      ]);
    } finally {
      await transaction.release();
    }

    return true;
  }

  /**
   * -------------------------------------------------------
   * Admin
   * delete order item
   */
  async deleteOrderItem(orderItemId: number) {
    const orderItem = await this.orderItemRepository.findOne({
      where: { id: orderItemId },
      relations: ['product', 'order', 'order.car'],
    });

    const order = { ...orderItem.order };
    const productId = orderItem.productId;
    const increaseQuantity = orderItem.quantity;

    const isOilGovernment = order.governmentOilTrackingCode !== null;

    if (orderItem.order.paymentStatusCode === OrderPaymentStatus.paid) {
      this.error.methodNotAllowed([
        'اجازه تغییر در فاکتور پرداخت شده وجود ندارد',
      ]);
    }

    const countOrderItems = await this.orderItemRepository.count({
      orderId: order.id,
    });
    if (countOrderItems === 1) {
      this.error.methodNotAllowed([
        'امکان حذف این آیتم وجود ندارد، حداقل یک آیتم در فاکتور باید وجود داشته باشد ',
      ]);
    }

    if (
      order.paymentStatusCode === OrderPaymentStatus.unsettledPaid &&
      order.snappPayPaymentToken &&
      order.finalAmount <
        order.finalAmount - orderItem.price * orderItem.quantity
    ) {
      this.error.methodNotAllowed([
        'بدلیل اینکه این فاکتور از طریق اسنپ پی پرداخت شده است، باید مبلغ نهایی جدید کمتر از مبلغ قبل از ویرایش فاکتور باشد',
      ]);
    }

    // Using SQL transaction for rollback
    const transaction = new Transaction();
    const queryRunner = await transaction.start();

    try {
      // Deletting
      await queryRunner.manager.remove(orderItem);

      // Finding all order items ( new )
      const productItems: any = await this.orderItemRepository.find({
        where: { orderId: order.id },
        order: { id: 'ASC' },
      });

      // Calculating
      const {
        orderItems,
        orderTotal,
        wageTotal,
        vat,
        orderShipping,
        deductionsAmount,
        finalAmount,
        orderDiscount,
        couponDiscount,
        orderTax,
      } = await this.calculateFinalAmount({
        ...order,
        productItems,
        makerBrandId: order?.car?.makerBrandId || null,
        isNewOrder: false,
        isOilGovernment,
      });

      if (order.isService) {
        for (let i = 0; i < productItems.length; i++) {
          const productItem = productItems[i];

          const orderItem = orderItems.find(
            (o) => o.orderItemId === productItem.id,
          );
          productItem.wagePrice = orderItem.wagePrice;
          productItem.tax = orderItem.tax;
          await queryRunner.manager.save(productItem);
        }
      }

      // Updating order
      await this.orderRepository
        .createQueryBuilder('order', queryRunner)
        .update()
        .set({
          orderTotal,
          wageTotal,
          vat,
          orderTax,
          couponDiscount,
          orderShipping,
          orderDiscount,
          deductionsAmount,
          finalAmount,
          updatedAt: new Date(),
        })
        .where({ id: order.id })
        .execute();

      // Updating the product stock quantity
      await this.productService.updateStockQuantity(
        productId,
        +increaseQuantity,
        isOilGovernment,
        queryRunner,
      );

      await transaction.commit();

      // update request to snapp pay
      if (
        order.paymentStatusCode === OrderPaymentStatus.unsettledPaid &&
        order.snappPayPaymentToken
      ) {
        const data = await this.prepareSnappPayCartList(order.id);
        await this.snappPayService.updateRequest(
          order.snappPayPaymentToken,
          data.cartList,
          data.finalAmount,
          data.discountAmount,
          data.shippingAmount,
          data.externalSourceAmount,
        );
      }
    } catch (e) {
      console.log(e);
      await transaction.rollback();
      this.error.internalServerError([
        'در ویرایش فاکتور خطایی رخ داده است با پشتیبانی تماس حاصل نمایید!',
      ]);
    } finally {
      await transaction.release();
    }
  }

  /**
   * -------------------------------------------------------
   * Admin
   * Get report all
   */
  async getReportAll(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    generateExcel = false,
  ) {
    let builder = this.orderRepository
      .createQueryBuilder('order')
      .innerJoin('order.user', 'user')
      .innerJoin('order.orderItems', 'orderItems')
      .innerJoin('orderItems.product', 'product')
      .andWhere('order.isDeleted = 0')
      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.paid,
      })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .select([
        'order.id',
        'order.orderNumber',
        'order.isService',
        'order.userId',
        'order.orderShipping',
        'order.deductionsAmount',
        'order.orderTotal',
        'order.orderDiscount',
        'order.wageTotal',
        'order.orderTax',
        'order.vat',
        'order.finalAmount',
        'order.createdAt',

        'orderItems.id',
        'orderItems.price',
        'orderItems.quantity',
        'orderItems.discount',
        'orderItems.wagePrice',

        'product.id',
        'product.productId',
        'product.name',

        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
      ]);

    builder = applyFiltersToBuilder(builder, filters);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.userId', 'ASC');
      builder.addOrderBy('order.createdAt', 'ASC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    // -------------------
    if (generateExcel) {
      const workbook = new Workbook();
      const worksheet = workbook.addWorksheet(`sheet1`);
      worksheet.views = [{ rightToLeft: true }];

      worksheet.addRow([
        'شماره فاکتور',
        'مشتری',
        'موبایل',
        'کد محصول',
        'نام محصول',
        'مبلغ واحد',
        'تعداد',
        'تخفیف',
        'اجرت',
        'ارزش افزوده',
        'ایاب و ذهاب',
        'کسورات',
        'مبلغ نهایی',
        'تاریخ',
      ]);

      items.forEach((order) => {
        // const totalQuantity = order.orderItems.reduce(
        //   (acc, orderItem) => acc + orderItem.quantity,
        //   0,
        // );

        const row = worksheet.addRow([
          order.orderNumber || '-',
          `${order.user.name || ''} ${order.user.surName || ''}`,
          order.user.mobile || '-',
          '',
          '',
          order.orderTotal,
          '',
          order.orderDiscount,
          order.wageTotal || 0,
          order.vat || 0,
          order.orderShipping || 0,
          order.deductionsAmount || 0,
          order.finalAmount || this._calculateFinalPrice(order),
          order.createdAt
            ? moment(order.createdAt).format('jYYYY-jMM-jDD')
            : '',
        ]);
        row.fill = {
          type: 'pattern',
          pattern: 'solid',
          fgColor: { argb: 'FFB8CCE4' },
        };

        order.orderItems.forEach((orderItem) => {
          worksheet.addRow([
            '',
            '',
            '',
            orderItem.product.productId,
            orderItem.product.name,
            orderItem.price,
            orderItem.quantity,
            orderItem.discount,
            orderItem.wagePrice,
            '',
            '',
            '',
            (orderItem.price - orderItem.discount) * orderItem.quantity +
              orderItem.wagePrice,
            '',
          ]);
        });
      });

      worksheet.getRow(1).fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFBFBFBF' },
      };

      worksheet.getColumn(1).width = 16;
      worksheet.getColumn(2).width = 14;
      worksheet.getColumn(3).width = 18;
      worksheet.getColumn(4).width = 18;
      worksheet.getColumn(5).width = 38;
      worksheet.getColumn(6).width = 12;
      worksheet.getColumn(7).width = 7;
      worksheet.getColumn(8).width = 7;
      worksheet.getColumn(9).width = 10;
      worksheet.getColumn(10).width = 12;
      worksheet.getColumn(11).width = 12;
      worksheet.getColumn(12).width = 11;
      worksheet.getColumn(13).width = 11;
      worksheet.getColumn(14).width = 12;

      // Save on tmp and export excel file
      try {
        const tmpobj = tmp.fileSync({
          mode: 0o644,
          prefix: `report_shipping_${moment().format('YYYY-MM-DD')}`,
          postfix: '.xlsx',
          discardDescriptor: true,
        });
        await workbook.xlsx.writeFile(tmpobj.name);
        return tmpobj.name;
      } catch (err) {
        console.log(err);
        this.error.internalServerError([
          'در تولید فایل اکسل خطایی رخ داده است',
        ]);
      }
    }

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

  /**
   * -------------------------------------------------------
   * Admin
   * Get report wages
   */
  async getReportWages(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    generateExcel = false,
  ) {
    let builder = this.orderItemRepository
      .createQueryBuilder('orderItem')
      .innerJoin('orderItem.product', 'product')
      .innerJoin('orderItem.order', 'order')
      .innerJoin('order.user', 'user')

      .andWhere('order.isDeleted = 0')
      .andWhere('order.isService = 1')
      .andWhere('orderItem.wagePrice > 1')

      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.paid,
      })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .select([
        'orderItem.id',
        'orderItem.wagePrice',

        'order.id',
        'order.orderNumber',
        'order.isService',
        'order.userId',
        'order.createdAt',

        'product.id',
        'product.name',

        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
      ]);

    builder = applyFiltersToBuilder(builder, filters);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.userId', 'ASC');
      builder.addOrderBy('order.createdAt', 'ASC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    // -------------------
    if (generateExcel) {
      const workbook = new Workbook();
      const worksheet = workbook.addWorksheet(`sheet1`);
      worksheet.views = [{ rightToLeft: true }];

      worksheet.addRow([
        'شماره فاکتور',
        'مشتری',
        'موبایل',
        'محصول',
        'اجرت',
        'تاریخ',
      ]);

      items.forEach((orderItem) => {
        worksheet.addRow([
          orderItem.order.orderNumber || '-',
          `${orderItem.order.user.name || ''} ${
            orderItem.order.user.surName || ''
          }`,
          orderItem.order.user.mobile || '-',
          orderItem.product.name || '-',
          orderItem.wagePrice || 0,
          orderItem.order.createdAt
            ? moment(orderItem.order.createdAt).format('jYYYY-jMM-jDD')
            : '',
        ]);
      });

      worksheet.getRow(1).fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFBFBFBF' },
      };

      worksheet.getColumn(1).width = 28;
      worksheet.getColumn(2).width = 24;
      worksheet.getColumn(3).width = 24;
      worksheet.getColumn(4).width = 12;
      worksheet.getColumn(5).width = 12;
      worksheet.getColumn(6).width = 18;

      // Save on tmp and export excel file
      try {
        const tmpobj = tmp.fileSync({
          mode: 0o644,
          prefix: `report_wages_${moment().format('YYYY-MM-DD')}`,
          postfix: '.xlsx',
          discardDescriptor: true,
        });
        await workbook.xlsx.writeFile(tmpobj.name);
        return tmpobj.name;
      } catch (err) {
        console.log(err);
        this.error.internalServerError([
          'در تولید فایل اکسل خطایی رخ داده است',
        ]);
      }
    }

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

  /**
   * -------------------------------------------------------
   * Admin
   * Get report shipping
   */
  async getReportShipping(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    generateExcel = false,
  ) {
    let builder = this.orderRepository
      .createQueryBuilder('order')
      .innerJoin('order.user', 'user')
      .andWhere('order.isDeleted = 0')
      .andWhere('order.isService = 1')
      .andWhere('order.orderShipping > 0')
      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.paid,
      })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .select([
        'order.id',
        'order.orderNumber',
        'order.isService',
        'order.userId',
        'order.orderShipping',
        'order.deductionsAmount',
        'order.createdAt',

        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
      ]);

    builder = applyFiltersToBuilder(builder, filters);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.userId', 'ASC');
      builder.addOrderBy('order.createdAt', 'ASC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    // -------------------
    if (generateExcel) {
      const workbook = new Workbook();
      const worksheet = workbook.addWorksheet(`sheet1`);
      worksheet.views = [{ rightToLeft: true }];

      worksheet.addRow([
        'شماره فاکتور',
        'مشتری',
        'موبایل',
        'ایاب و ذهاب',
        'کسورات',
        'تاریخ',
      ]);

      items.forEach((order) => {
        worksheet.addRow([
          order.orderNumber || '-',
          `${order.user.name || ''} ${order.user.surName || ''}`,
          order.user.mobile || '-',
          order.orderShipping || 0,
          order.deductionsAmount || 0,
          order.createdAt
            ? moment(order.createdAt).format('jYYYY-jMM-jDD')
            : '',
        ]);
      });

      worksheet.getRow(1).fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFBFBFBF' },
      };

      worksheet.getColumn(1).width = 28;
      worksheet.getColumn(2).width = 24;
      worksheet.getColumn(3).width = 24;
      worksheet.getColumn(4).width = 12;
      worksheet.getColumn(5).width = 12;
      worksheet.getColumn(6).width = 12;

      // Save on tmp and export excel file
      try {
        const tmpobj = tmp.fileSync({
          mode: 0o644,
          prefix: `report_shipping_${moment().format('YYYY-MM-DD')}`,
          postfix: '.xlsx',
          discardDescriptor: true,
        });
        await workbook.xlsx.writeFile(tmpobj.name);
        return tmpobj.name;
      } catch (err) {
        console.log(err);
        this.error.internalServerError([
          'در تولید فایل اکسل خطایی رخ داده است',
        ]);
      }
    }

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

  /**
   * -------------------------------------------------------
   * Admin
   * Get report unsettled paid
   */
  async getReportUnsettledPaid(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    generateExcel = false,
  ) {
    let builder = this.orderRepository
      .createQueryBuilder('order')
      .innerJoin('order.user', 'user')
      .leftJoin('order.payments', 'payments')
      .andWhere('order.isDeleted = 0')
      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.unsettledPaid,
      })
      .andWhere('order.orderStatusCode != :cancelStatus', {
        cancelStatus: OrderStatus.canceled,
      })
      .select([
        'order.id',
        'order.orderNumber',
        'order.isService',
        'order.userId',
        'order.usedWalletAmount',
        'order.finalAmount',
        'order.createdAt',

        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',

        'payments.id',
        'payments.businessPartnerCode',
      ]);

    builder = applyFiltersToBuilder(builder, filters);

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

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    // -------------------
    if (generateExcel) {
      const workbook = new Workbook();
      const worksheet = workbook.addWorksheet(`sheet1`);
      worksheet.views = [{ rightToLeft: true }];

      worksheet.addRow([
        'شماره فاکتور',
        'مشتری',
        'موبایل',
        'مبلغ پرداخت شده (تومان)',
        'تاریخ',
      ]);

      items.forEach((order) => {
        worksheet.addRow([
          order.orderNumber || '-',
          `${order.user.name || ''} ${order.user.surName || ''}`,
          order.user.mobile || '-',
          (order.finalAmount || 0) - (order.usedWalletAmount || 0),
          order.createdAt
            ? moment(order.createdAt).format('jYYYY-jMM-jDD')
            : '',
        ]);
      });

      worksheet.getRow(1).fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFBFBFBF' },
      };

      worksheet.getColumn(1).width = 28;
      worksheet.getColumn(2).width = 24;
      worksheet.getColumn(3).width = 24;
      worksheet.getColumn(4).width = 24;
      worksheet.getColumn(5).width = 12;

      // Save on tmp and export excel file
      try {
        const tmpobj = tmp.fileSync({
          mode: 0o644,
          prefix: `report_shipping_${moment().format('YYYY-MM-DD')}`,
          postfix: '.xlsx',
          discardDescriptor: true,
        });
        await workbook.xlsx.writeFile(tmpobj.name);
        return tmpobj.name;
      } catch (err) {
        console.log(err);
        this.error.internalServerError([
          'در تولید فایل اکسل خطایی رخ داده است',
        ]);
      }
    }

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

  /**
   * -------------------------------------------------------
   */
  async unsettledsToPaid() {
    await this.orderRepository
      .createQueryBuilder()
      .update()
      .set({ paymentStatusCode: OrderPaymentStatus.paid })
      .andWhere('isDeleted = 0')
      .andWhere('paymentStatusCode = :status', {
        status: OrderPaymentStatus.unsettledPaid,
      })
      .andWhere('orderStatusCode != :cancelStatus', {
        cancelStatus: OrderStatus.canceled,
      })
      .execute();
  }

  /**
   * -------------------------------------------------------
   * Admin
   * Get report shopping
   */
  async getReportShopping(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    generateExcel = false,
    generateExcelSshaf = false,
  ) {
    let builder = this.orderItemRepository
      .createQueryBuilder('orderItem')
      .innerJoin('orderItem.product', 'product')
      .innerJoin('product.category', 'category', 'category.isService = 0')
      .innerJoin('orderItem.order', 'order')
      .innerJoin('order.user', 'user')
      .andWhere('order.isDeleted = 0')
      .andWhere('order.paymentStatusCode = :status', {
        status: OrderPaymentStatus.paid,
      })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .select([
        'orderItem.id',
        'orderItem.price',
        'orderItem.quantity',
        'orderItem.discount',

        'order.id',
        'order.orderNumber',
        'order.isService',
        'order.userId',
        'order.finalAmount',
        'order.createdAt',

        'product.id',
        'product.productId',
        'product.name',
        'product.weight',

        'category.id',
        'category.name',

        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
      ]);

    if (generateExcelSshaf) {
      builder.leftJoin('product.manufacturer', 'manufacturer');
      builder.leftJoin('order.car', 'car');
      builder.leftJoin('order.address', 'address');
      builder.addSelect([
        'user.nationalCode',
        'car.id',
        'car.number',
        'car.vinCode',
        'car.engineCode',
        'address.id',
        'address.address',
        'manufacturer.id',
        'manufacturer.name',
      ]);
    }

    builder = applyFiltersToBuilder(builder, filters);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.userId', 'ASC');
      builder.addOrderBy('order.createdAt', 'ASC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    // -------------------
    if (generateExcel || generateExcelSshaf) {
      const workbook = new Workbook();
      const worksheet = workbook.addWorksheet(`sheet1`);
      worksheet.views = [{ rightToLeft: true }];

      if (generateExcel) {
        worksheet.addRow([
          'شماره فاکتور',
          'مشتری',
          'موبایل',
          'محصول',
          'مبلغ واحد',
          'تعداد',
          'تخفیف',
          'مبلغ نهایی',
          'تاریخ',
        ]);

        items.forEach((orderItem) => {
          worksheet.addRow([
            orderItem.order.orderNumber || '-',
            `${orderItem.order.user.name || ''} ${
              orderItem.order.user.surName || ''
            }`,
            orderItem.order.user.mobile || '-',
            orderItem.product.name || '-',
            orderItem.price || 0,
            orderItem.quantity || 0,
            orderItem.discount || 0,
            (orderItem.price - orderItem.discount) * orderItem.quantity || 0,
            orderItem.order.createdAt
              ? moment(orderItem.order.createdAt).format('jYYYY-jMM-jDD')
              : '',
          ]);
        });
      }

      if (generateExcelSshaf) {
        worksheet.addRow([
          'تاریخ فروش',
          'کد ملی خریدار',
          'نام و نام خانوادگی خریدار',
          'شماره موبایل خریدار',
          'برند',
          'کد کالا',
          'تعداد',
          'قیمت فی (ریال)',
          'قیمت کل (ریال)',
          'شماره پلاک خودرو',
          'Vin Code',
          'Engine Code',
          'لیتر',
          'آدرس خریدار',
        ]);

        items.forEach((orderItem) => {
          worksheet.addRow([
            orderItem.order.createdAt
              ? moment(orderItem.order.createdAt).format('jYYYY/jMM/jDD')
              : '',
            orderItem.order.user?.nationalCode || '',
            `${orderItem.order.user?.name || ''} ${
              orderItem.order.user?.surName || ''
            }`.trim(),
            orderItem.order.user?.mobile || '',
            orderItem.product?.manufacturer?.name || '',
            `${orderItem.product?.productId || ''}`,
            orderItem.quantity || 0,
            (orderItem.price || 0) * 10,
            (orderItem.price || 0) * (orderItem.quantity || 0) * 10,
            (orderItem.order?.car?.number || '').replace(/\*\*\*/g, ' '),
            orderItem.order?.car?.vinCode || '',
            orderItem.order?.car?.engineCode || '',
            +(orderItem.product?.weight || '0') * (orderItem.quantity || 0),
            orderItem.order?.address?.address || '',
          ]);
        });
      }

      worksheet.getRow(1).fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFBFBFBF' },
      };

      worksheet.getColumn(1).width = 28;
      worksheet.getColumn(2).width = 24;
      worksheet.getColumn(3).width = 24;
      worksheet.getColumn(4).width = 12;
      worksheet.getColumn(5).width = 12;
      worksheet.getColumn(6).width = 18;
      worksheet.getColumn(7).width = 15;
      worksheet.getColumn(8).width = 15;
      worksheet.getColumn(9).width = 15;
      worksheet.getColumn(10).width = 15;
      worksheet.getColumn(11).width = 15;

      // Save on tmp and export excel file
      try {
        const tmpobj = tmp.fileSync({
          mode: 0o644,
          prefix: `report_shopping_${moment().format('YYYY-MM-DD')}`,
          postfix: '.xlsx',
          discardDescriptor: true,
        });
        await workbook.xlsx.writeFile(tmpobj.name);
        return tmpobj.name;
      } catch (err) {
        console.log(err);
        this.error.internalServerError([
          'در تولید فایل اکسل خطایی رخ داده است',
        ]);
      }
    }

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

  /**
   * -------------------------------------------------------
   * Admin
   * Get orders list
   */
  async getOrders(page = 1, limit = 20, sorts = null, filters = null) {
    let builder = this.orderRepository.createQueryBuilder('order');

    builder.leftJoin('order.user', 'user');
    builder.leftJoin('order.payments', 'payments');
    builder.leftJoin('order.serviceMan', 'serviceMan');
    builder.leftJoin('order.address', 'address'); // order -> address
    builder.leftJoin('order.seller', 'seller');
    builder.leftJoin('address.city', 'city'); // order -> address -> city

    builder.select([
      'order.id',
      'order.orderNumber',
      'order.orderStatusCode',
      'order.paymentStatusCode',
      'order.orderTax',
      'order.vat',
      'order.orderDiscount',
      'order.orderShipping',
      'order.shippingType',
      'order.orderTotal',
      'order.wageTotal',
      'order.clientType',
      'order.paymentMethodSystemName',
      'order.governmentOilTrackingCode',
      'order.adminComment',
      'order.isService',
      'order.paymentGateway',
      'order.createdAt',

      'user.id',
      'user.name',
      'user.surName',

      'serviceMan.id',
      'serviceMan.name',
      'serviceMan.surName',

      'address.id',
      'city.id',
      'city.name',

      'seller.id',
      'seller.name',
      'seller.surName',

      'payments.id',
      'payments.businessPartnerCode',
    ]);

    builder = applyFiltersToBuilder(builder, filters);
    builder.andWhere({ isDeleted: false });

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.createdAt', 'DESC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    return {
      items: items.map((item) => ({
        ...item,
        finalPrice: item.finalAmount || this._calculateFinalPrice(item),
      })),
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   * Service-man
   * Get services list
   */
  async getServicesByServiceMan(
    userId: string,
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
  ) {
    let builder = this.orderRepository.createQueryBuilder('order');

    builder.leftJoin('order.user', 'user');
    builder.leftJoin('order.address', 'address'); // order -> address
    builder.leftJoin('address.city', 'city'); // order -> address -> city

    builder.andWhere({ serviceManId: userId });

    builder.select([
      'order.id',
      'order.orderNumber',
      'order.orderStatusCode',
      'order.paymentStatusCode',
      'order.orderTax',
      'order.vat',
      'order.orderDiscount',
      'order.orderShipping',
      'order.orderTotal',
      'order.wageTotal',
      'order.paymentMethodSystemName',
      'order.createdAt',

      'user.id',
      'user.name',
      'user.surName',

      'address.id',
      'city.id',
      'city.name',
    ]);

    builder = applyFiltersToBuilder(builder, filters);
    builder.andWhere({ isDeleted: false });

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('order.createdAt', 'DESC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    return {
      items: items.map((item) => ({
        ...item,
        finalPrice: item.finalAmount || this._calculateFinalPrice(item),
      })),
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   * Admin
   * Get orders by id
   */
  async getOrderById(orderId: number) {
    const builder = this.orderRepository.createQueryBuilder('order');

    builder.leftJoin('order.user', 'user');
    builder.leftJoin('order.serviceMan', 'serviceMan');
    builder.leftJoinAndSelect('order.address', 'address');
    builder.leftJoin('order.seller', 'seller');
    builder.leftJoinAndSelect('address.city', 'city'); // order -> address -> city
    builder.leftJoinAndSelect('city.state', 'state'); // address -> city -> state
    builder.leftJoinAndSelect('order.car', 'car');
    builder.leftJoinAndSelect('car.makerBrand', 'makerBrand'); // order -> car -> makerBrand
    builder.leftJoinAndSelect('order.orderItems', 'orderItems');
    builder.leftJoinAndSelect('orderItems.product', 'product'); // order -> orderItems -> product
    builder.leftJoinAndSelect('product.quantityUnit', 'quantityUnit'); // order -> orderItems -> product
    builder.leftJoinAndSelect('order.polls', 'polls');
    builder.leftJoinAndSelect('order.payments', 'payments');

    builder.addSelect([
      'user.id',
      'user.name',
      'user.surName',
      'user.mobile',
      'user.phone',
      'user.address',
      'user.email',
      'user.nationalCode',

      'serviceMan.id',
      'serviceMan.name',
      'serviceMan.surName',
      'serviceMan.mobile',

      'seller.id',
      'seller.name',
      'seller.surName',
    ]);

    builder.where({ id: orderId });

    const item = await builder.getOne();

    let allowRequestAlopeyk = false;
    let allowRequestMahex = false;

    if (+item.shippingType !== 6) {
      if (
        !item.isService &&
        [CityIds.tehran, CityIds.karaj].indexOf(item.address?.cityId) !== -1
      ) {
        allowRequestAlopeyk = true;
      }

      if (
        item?.address?.cityId &&
        !item.isService &&
        [CityIds.tehran, CityIds.karaj].indexOf(item.address.cityId) === -1 &&
        item.address.city.mahexCode
      ) {
        allowRequestMahex = true;
      }
    }

    let allowEdit = false;
    if (item.paymentStatusCode !== OrderPaymentStatus.paid) {
      allowEdit = true;
    }

    return {
      ...item,
      allowEdit,
      allowRequestAlopeyk,
      allowRequestMahex,
      finalPrice: item.finalAmount || this._calculateFinalPrice(item),
    };
  }

  /**
   * -------------------------------------------------------
   * Service-man
   * Get services by id
   */
  async getServiceWithIdByServiceMan(userId: string, orderId: number) {
    const builder = this.orderRepository.createQueryBuilder('order');

    builder.leftJoin('order.user', 'user');
    builder.leftJoin('order.serviceMan', 'serviceMan');
    builder.leftJoinAndSelect('order.address', 'address');
    builder.leftJoinAndSelect('address.city', 'city'); // order -> address -> city
    builder.leftJoinAndSelect('city.state', 'state'); // address -> city -> state
    builder.leftJoinAndSelect('order.car', 'car');
    builder.leftJoinAndSelect('car.makerBrand', 'makerBrand'); // order -> car -> makerBrand
    builder.leftJoinAndSelect('order.orderItems', 'orderItems');
    builder.leftJoinAndSelect('orderItems.product', 'product'); // order -> orderItems -> product
    builder.leftJoinAndSelect('product.quantityUnit', 'quantityUnit'); // order -> orderItems -> product

    builder.addSelect([
      'user.id',
      'user.name',
      'user.surName',
      'user.mobile',
      'user.phone',
      'user.address',
      'user.email',

      'serviceMan.id',
      'serviceMan.name',
      'serviceMan.surName',
      'serviceMan.mobile',
    ]);

    builder.where({ id: orderId, serviceManId: userId });

    const item = await builder.getOne();
    return {
      ...item,
      finalPrice: item.finalAmount || this._calculateFinalPrice(item),
    };
  }

  /**
   * -------------------------------------------------------
   * Cron Job
   *
   * SELECT * FROM `orders`
   *  WHERE
   *    `isService` = 1 AND
   *    `orderStatusCode` != 1 AND
   *    `isDeleted` = 0 AND
   *      (DATEDIFF(CURDATE(), `createdAt`) * (`minKilometersDay` + IFNULL(`maxKilometersDay`, 90)) / 2 > 5000
   *        OR
   *        DATEDIFF(CURDATE(), `createdAt`) > 180)
   */
  async findAllDeadlineService(deadlineKilometer = 5000, deadlineDay = 180) {
    return await this.orderRepository
      .createQueryBuilder('order')
      .select([
        'order.id',
        'user.id',
        'user.email',
        'user.name',
        'user.surName',
        'user.mobile',
        'car.id',
        'car.model',
        'car.number',
        'makerBrand.id',
        'makerBrand.name',
        'serviceReminders.id',
        'serviceReminders.lastSmsKilometer',
      ])
      .leftJoin('order.serviceReminders', 'serviceReminders')
      .leftJoin('order.user', 'user')
      .leftJoin('order.car', 'car')
      .leftJoin('car.makerBrand', 'makerBrand')
      .leftJoinAndMapOne(
        'order.relatedOrder',
        OrderEntity,
        'related',
        'related.carId = order.carId AND related.userId = order.userId AND related.isService = 1 AND related.createdAt > order.createdAt',
      )
      .where({ isService: true })
      .andWhere({ isDeleted: false })
      .andWhere('order.orderStatusCode != :code', {
        code: OrderStatus.pending,
      })
      .andWhere('order.orderStatusCode != :code', {
        code: OrderStatus.canceled,
      })
      .andWhere('order.minKilometersDay IS NOT NULL')
      .andWhere(
        '(DATEDIFF(CURDATE(), order.createdAt) * (order.minKilometersDay + IFNULL(order.maxKilometersDay, 90)) / 2 > :deadlineKilometer OR DATEDIFF(CURDATE(), order.createdAt) > :deadlineDay)',
        { deadlineKilometer, deadlineDay },
      )
      .andWhere('related.id IS NULL')
      .andWhere('car.id IS NOT NULL')
      .andWhere('(car.model IS NOT NULL OR car.makerBrand IS NOT NULL)')
      .getMany();
  }

  /**
   * -------------------------------------------------------
   */
  private _calculateFinalPrice(item: OrderEntity) {
    return (
      (item.orderTotal || 0) +
      (item.wageTotal || 0) +
      (item.orderTax || 0) +
      (item.vat || 0) +
      (item.orderShipping || 0) -
      (item.orderDiscount || 0) -
      (item.deductionsAmount || 0)
    );
  }

  /**
   * -------------------------------------------------------
   * show chart of orders
   * GET /orders/statistic/chart/admin
   */
  async statisticChart(
    from,
    to,
    type = 'daily',
    section,
    userId: number,
    accessAll = false,
  ) {
    const builder = this.orderRepository
      .createQueryBuilder('order')
      .groupBy('DAY(order.createdAt), MONTH(order.createdAt)')
      .andWhere({ paymentStatusCode: OrderPaymentStatus.paid })
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .andWhere('DATE(order.createdAt) BETWEEN :from AND :to', {
        from,
        to,
      })
      .orderBy('order.createdAt', 'ASC');

    if (section === 'service') {
      builder.andWhere('order.isService = 1');
    } else if (section === 'shop') {
      builder.andWhere('order.isService = 0');
    }

    if (accessAll) {
      builder.select([
        'SUM(IFNULL(order.orderTotal, 0) + IFNULL(order.wageTotal, 0) + IFNULL(order.orderTax, 0) + IFNULL(order.vat, 0) + IFNULL(order.orderShipping, 0) - IFNULL(order.orderDiscount, 0) - IFNULL(order.deductionsAmount, 0)) AS sumCalFinalAmount',
        'SUM(order.finalAmount) AS sumFinalAmount',
        'DAY(order.createdAt) AS day',
        'MONTH(order.createdAt) AS month',
        'DATE(order.createdAt) AS date',
      ]);
    } else {
      builder
        .innerJoin('order.orderItems', 'orderItems')
        .innerJoin('orderItems.product', 'product')
        .andWhere('product.creatorUserId = :userId', { userId })
        .select([
          'SUM(IFNULL(orderItems.price, 0) + IFNULL(orderItems.quantity, 0)) AS sumCalFinalAmount',
          'DAY(order.createdAt) AS day',
          'MONTH(order.createdAt) AS month',
          'DATE(order.createdAt) AS date',
        ]);
    }

    const list = await builder.getRawMany();

    const mapping = list.map((item) => ({
      sum: +item?.sumFinalAmount || +item?.sumCalFinalAmount || 0,
      label: moment(item.date).locale('fa').format('D (ddd)'),
      date: item.date,
    }));

    if (type === 'daily') {
      return mapping;
    }

    const obj = {};
    mapping.forEach((item) => {
      const m = moment(item.date).format('jM');

      obj[+m] = {
        label: moment(item.date).locale('fa').format('MMMM'),
        month: +m,
        sum: (obj[+m]?.sum || 0) + item.sum,
      };
    });

    return Object.values(obj).sort((a: any, b: any) => a.month - b.month);
  }

  /**
   * -------------------------------------------------------
   */
  async statistic() {
    // Suspended order statistics
    const shopPending = await this.orderRepository.count({
      orderStatusCode: OrderStatus.pending,
      isService: false,
      isDeleted: false,
    });

    const servicePending = await this.orderRepository.count({
      orderStatusCode: OrderStatus.pending,
      isService: true,
      isDeleted: false,
    });

    // all sales(shop + service) in current month and last montth statistics
    const startOfCurrentMonth = moment().startOf('month').toDate();
    const startOfLastMonth = moment()
      .startOf('month')
      .subtract(1, 'month')
      .toDate();

    // sales statistics
    const shopSalesTotal = await this._statisticSales(false);
    const shopSalesMonth = await this._statisticSales(
      false,
      MoreThan(startOfCurrentMonth),
    ); // now - 1 month age
    const shopSalesLastMonth = await this._statisticSales(
      false,
      Between(startOfLastMonth, startOfCurrentMonth),
    ); // 1 month ago - 2 month ago

    const serviceSalesTotal = await this._statisticSales(true);
    const serviceSalesMonth = await this._statisticSales(
      true,
      MoreThan(startOfCurrentMonth),
    ); // now - 1 month age
    const serviceSalesLastMonth = await this._statisticSales(
      true,
      Between(startOfLastMonth, startOfCurrentMonth),
    ); // 1 month ago - 2 month ago

    return {
      shopPending,
      servicePending,
      shopSales: {
        total: shopSalesTotal,
        month: shopSalesMonth,
        lastMonth: shopSalesLastMonth,
      },
      serviceSales: {
        total: serviceSalesTotal,
        month: serviceSalesMonth,
        lastMonth: serviceSalesLastMonth,
      },
      salesCountByCategory: await this._statisticByCategory(),
      salesCountByProduct: await this._statisticByProduct(),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async statisticServiceMan(serviceManId: string) {
    const { pending, accepted, sent, delivered, canceled } =
      await this.orderRepository
        .createQueryBuilder('order')
        .andWhere('order.isService = 1')
        .andWhere('order.isDeleted = 0')
        .andWhere('order.serviceManId = :serviceManId', { serviceManId })
        .select([
          `SUM(IF(order.orderStatusCode = ${OrderStatus.pending}, 1, 0)) AS pending`,
          `SUM(IF(order.orderStatusCode = ${OrderStatus.accepted}, 1, 0)) AS accepted`,
          `SUM(IF(order.orderStatusCode = ${OrderStatus.sent}, 1, 0)) AS sent`,
          `SUM(IF(order.orderStatusCode = ${OrderStatus.delivered}, 1, 0)) AS delivered`,
          `SUM(IF(order.orderStatusCode = ${OrderStatus.canceled}, 1, 0)) AS canceled`,
        ])
        .getRawOne();

    return {
      pending: +pending,
      accepted: +accepted,
      sent: +sent,
      delivered: +delivered,
      canceled: +canceled,
    };
  }

  /**
   * -------------------------------------------------------
   */
  private async _statisticSales(isService = true, createdAt = null) {
    const rows = await this.orderRepository.find({
      where: {
        orderStatusCode: In([
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ]),
        paymentStatusCode: OrderPaymentStatus.paid,
        isService,
        ...(createdAt && { createdAt }),
      },
      select: [
        'orderTotal',
        'orderTotal',
        'wageTotal',
        'vat',
        'orderTax',
        'orderDiscount',
      ],
    });

    return {
      count: rows.length,
      sum: rows.reduce(
        (sum, order) =>
          sum +
          order.orderTotal +
          order.wageTotal +
          order.vat +
          order.orderTax -
          order.orderDiscount,
        0,
      ),
    };
  }

  /**
   * -------------------------------------------------------
   */
  private async _statisticByCategory() {
    const list = await this.orderRepository
      .createQueryBuilder('order')
      .innerJoin('order.orderItems', 'orderItems')
      .innerJoin('orderItems.product', 'product')
      .innerJoin('product.category', 'category')
      .groupBy('category.id')
      .select(['COUNT(category.id) AS cnt', 'category.name'])
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .andWhere('order.paymentStatusCode = :payStatus', {
        payStatus: OrderPaymentStatus.paid,
      })
      .orderBy('cnt', 'DESC')
      .limit(10)
      .getRawMany();

    return list.map((item) => ({
      categoryName: item.category_name,
      count: +item.cnt,
    }));
  }

  /**
   * -------------------------------------------------------
   */
  private async _statisticByProduct() {
    const list = await this.orderRepository
      .createQueryBuilder('order')
      .innerJoin('order.orderItems', 'orderItems')
      .innerJoin('orderItems.product', 'product')
      .innerJoin('product.category', 'category')
      .groupBy('product.id')
      .select(['COUNT(product.id) AS cnt', 'product.name'])
      .andWhere('order.orderStatusCode IN (:...statuses)', {
        statuses: [
          OrderStatus.sent,
          OrderStatus.accepted,
          OrderStatus.delivered,
        ],
      })
      .andWhere('order.paymentStatusCode = :payStatus', {
        payStatus: OrderPaymentStatus.paid,
      })
      .andWhere('category.isService = :isService', { isService: false })
      .orderBy('cnt', 'DESC')
      .limit(10)
      .getRawMany();

    return list.map((item) => ({
      productName: item.product_name,
      count: +item.cnt,
    }));
  }

  /**
   * -------------------------------------------------------
   */
  async handleDeleteUnpaid(longKeep = true) {
    let past: any = { value: 1, type: 'hour' };
    if (longKeep) {
      past = { value: 30, type: 'days' };
    }

    const pastTime = moment().subtract(past.value, past.type).toDate(); // 1 hour | 30 days

    // find orders unpaid and 24 hours createdat past
    let orders: OrderEntity[] = [];

    if (longKeep) {
      orders = await this.orderRepository.find({
        where: {
          orderStatusCode: OrderStatus.pending,
          paymentStatusCode: OrderPaymentStatus.pending,
          paymentMethodSystemName: OrderPaymentMethod.offline,
          isDeleted: false,
          createdAt: LessThan(pastTime),
        },
        relations: ['orderItems', 'orderItems.product'],
      });
    } else {
      orders = await this.orderRepository.find({
        where: {
          orderStatusCode: OrderStatus.pending,
          paymentStatusCode: OrderPaymentStatus.pending,
          paymentMethodSystemName: OrderPaymentMethod.online,
          shippingType: Not(OrderShippingType.inPerson),
          isDeleted: false,
          createdAt: LessThan(pastTime),
        },
        relations: ['orderItems', 'orderItems.product'],
      });
    }

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

      // if (
      //   order.governmentOilTrackingCode &&
      //   order.governmentOilTrackingCode !== '0'
      // ) {
      //   try {
      //     await this.samtService.CancelOrderOilOrTire(
      //       order.governmentOilTrackingCode,
      //       '2902348201638'
      //     );
      //   } catch (err) {}
      // }

      order.isDeleted = true;
      await order.save();

      // rollback product stock quantity
      await this.rollbackStockQuantity(order);

      // Adding a log
      await this.logService.add({
        type: LogType.order,
        action: LogAction.delete,
        client: LogClient.system,
        message: `به دلیل تأخیر در پرداخت، سفارش شماره #${order.orderNumber}، حذف گردید.`,
        affectedId: String(order.id),
        item: order,
      });
    }

    console.log(`ROWS AFFECTED: ${orders.length}`);

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  private async rollbackStockQuantity(order: OrderEntity) {
    for (let o = 0; o < order.orderItems.length; o++) {
      const orderItem = order.orderItems[o];

      // oil government
      if (
        order.governmentOilTrackingCode !== null &&
        orderItem.product.categoryId === 1
      ) {
        orderItem.product.governmentOilStockQuantity += orderItem.quantity;
      }
      // normal
      else {
        orderItem.product.stockQuantity += orderItem.quantity;
      }

      await orderItem.product.save();
    }
  }

  /**
   * -------------------------------------------------------
   */
  async updateStatus(
    orderId,
    dto: UpdateOrderStatusDto,
    operatorUserId: string,
  ) {
    const order = await this.orderRepository.findOne({
      where: { id: orderId },
      relations: ['user', 'orderItems', 'orderItems.product'],
    });
    const oldOrder = { ...order };

    const messages = ['سفارش مورد نظر با موفقیت تغییر وضعیت داده شد'];

    // Sending SMS
    if (order.orderStatusCode !== dto.orderStatusCode) {
      // sent serviceman or product
      if (dto.orderStatusCode === OrderStatus.sent) {
        if (order.isService) {
          await this.sms.sendNotifySendServiceman(
            order.user.mobile,
            order.user.surName || '-',
          );
          messages.push('پیامک ارسال سرویسکار برای مشتری ارسال شد');
        } else {
          await this.sms.sendNotifyInvoice(
            order.user.mobile,
            order.user.surName || '-',
            order.orderNumber,
          );
          messages.push('پیامک ارسال مرسوله برای مشتری ارسال شد');
        }
      }

      // complete service
      else if (
        dto.orderStatusCode === OrderStatus.delivered &&
        order.isService
      ) {
        let kilometer = 0;
        if (order.kilometers) {
          kilometer = order.kilometers + 5000;
        } else {
          const car = await this.carService.getCarRecordById(order.carId);
          kilometer = car ? car.kilometerNumber + 5000 : 0;
        }

        await this.sms.sendNotifyEndService(
          order.user.mobile,
          order.user.surName || '-',
          kilometer,
        );
        messages.push('پیامک اتمام سرویس برای مشتری ارسال شد');
      }

      // rollback product stock quantity
      else if (dto.orderStatusCode === OrderStatus.canceled) {
        if (order.snappPayPaymentToken) {
          await this.snappPayService.cancelRequest(order.snappPayPaymentToken);
        }
        await this.rollbackStockQuantity(order);
      }

      //
      else if (dto.orderStatusCode === OrderStatus.accepted) {
        // TODO: this is an accepted state, dont send inserting a new order
        //
        // const dateTime = `${order.date} ساعت ${order.time}`;
        // if (order.isService) {
        //   await this.sms.sendNotifAddServiceToCustomer(
        //     order.user.mobile,
        //     order.user.surName,
        //     dateTime,
        //     order.orderNumber,
        //   );
        //   messages.push('پیامک ثبت سرویس برای مشتری ارسال شد');
        // } else {
        //   await this.sms.sendNotifAddOrderToCustomer(
        //     order.user.mobile,
        //     order.user.surName,
        //     order.orderNumber,
        //   );
        //   messages.push('پیامک ثبت سفارش محصول برای مشتری ارسال شد');
        // }
      }
    }

    // Adding point to the user and the moaref
    if (
      order.paymentStatusCode !== dto.paymentStatusCode &&
      dto.paymentStatusCode === OrderPaymentStatus.paid
    ) {
      const pointValue = order.isService ? 2 : 1;

      await this.pointService.add(
        order.user,
        pointValue,
        `بابت فاکتور شماره ${order.orderNumber}`,
      );

      // log add point to user
      await this.logService.add({
        type: LogType.user,
        action: LogAction.insert,
        client: LogClient.system,
        message: `${pointValue} امتیاز به ${order.user.name} ${order.user.surName} بابت فاکتور شماره ${order.orderNumber} تخصیص داده شد.`,
        affectedId: order.user.id,
        item: {
          point: pointValue,
          orderId: order.id,
          orderNumber: order.orderNumber,
        },
      });

      messages.push(`${pointValue} امتیاز به مشتری مربوطه اختصاص داده شد`);

      const setPoint = await this.pointService.addToMoaref(
        order,
        1,
        `بابت فاکتور شماره ${order.orderNumber} کاربر معرفی شده، آقا/خانم ${order.user.name} ${order.user.surName}`,
      );
      if (setPoint) {
        messages.push(`1 امتیاز به معرف مشتری مربوطه اختصاص داده شد`);
      }
    }

    // Updating the order's status
    if (dto.orderStatusCode) {
      order.orderStatusCode = dto.orderStatusCode;
    }
    if (dto.paymentStatusCode) {
      order.paymentStatusCode = dto.paymentStatusCode;
    }
    if (dto.paymentMethodSystemName) {
      order.paymentMethodSystemName = dto.paymentMethodSystemName;
    }
    if (dto.adminComment) {
      order.adminComment = dto.adminComment;
    }
    await order.save();

    // log update status order by admin
    await this.logService.add({
      type: LogType.order,
      action: LogAction.update,
      operatorUserId,
      message: `سفارش به شماره #${order.orderNumber} تعیین وضعیت شد`,
      affectedId: String(order.id),
      item: {
        orderStatusCode: order.orderStatusCode,
        paymentStatusCode: order.paymentStatusCode,
        paymentMethodSystemName: order.paymentMethodSystemName,
      },
      oldItem: {
        orderStatusCode: oldOrder.orderStatusCode,
        paymentStatusCode: oldOrder.paymentStatusCode,
        paymentMethodSystemName: oldOrder.paymentMethodSystemName,
      },
    });

    return messages;
  }

  /**
   * -------------------------------------------------------
   * update service
   * assign service to service-man by admin
   */
  async updateService(dto: UpdateOrderServiceManDto, operatorUserId: string) {
    const order = await this.orderRepository.findOne({
      where: { id: dto.orderId },
      // relations: ['user'],
    });
    const oldOrder = { ...order };

    if (!order.isService) {
      this.error.methodNotAllowed(['اطلاعات ورودی نامعتبر است']);
    }

    order.serviceManId = dto.serviceManId;
    // order.orderStatusCode = OrderStatus.sent;
    await order.save();

    // // Sending SMS to the customer
    // await this.sms.sendNotifySendServiceman(
    //   order.user.mobile,
    //   order.user.surName,
    // );

    // Sending SMS to the service man
    const serviceMan = await this.userService.findById(dto.serviceManId);
    const dateTime = `${order.date}-${order.time}`;
    const encryptedToken = encryptId(order.id);

    await this.sms.sendNotifyOrderToServiceman(
      serviceMan.mobile,
      order.orderNumber,
      dateTime,
      encryptedToken,
    );

    // log
    await this.logService.add({
      type: LogType.order,
      action: LogAction.update,
      operatorUserId,
      message: `سفارش به شماره #${order.orderNumber} به ${serviceMan.name} ${serviceMan.surName} ارجاع داده شد.`,
      affectedId: String(order.id),
      item: order,
      oldItem: oldOrder,
    });

    return true;
  }

  /**
   * -------------------------------------------------------
   */
  async add(sellerId: string, dto: AddOrderDto) {
    const {
      orderItems,
      orderTotal,
      orderShipping,
      vat,
      deductionsAmount,
      finalAmount,
      orderDiscount,
      couponDiscount,
      orderTax,
    } = await this.calculateFinalAmount({
      ...dto,
      isService: false,
      isNewOrder: true,
      isOilGovernment: false,
    });

    const orderNumber = await this.generateOrderNumber();

    const { identifiers } = await this.orderRepository
      .createQueryBuilder()
      .insert()
      .values({
        ...dto,
        orderNumber,
        isService: false,
        clientType: OrderClientType.panel,
        sellerId,
        date: moment(new Date()).format('jYYYY/jMM/jDD'),
        orderGuid: uuidv4(),
        orderTotal,
        orderTax,
        vat,
        orderShipping,
        orderDiscount,
        couponDiscount,
        deductionsAmount,
        finalAmount,
        paymentMethodSystemName: dto.paymentMethod,
        createdAt: dto.createdAt || new Date(),
        updatedAt: new Date(),
      })
      .execute();

    const orderId = identifiers[0].id;

    // insert order items
    for (let i = 0; i < orderItems.length; i++) {
      const product = orderItems[i];

      await this.orderItemRepository
        .createQueryBuilder()
        .insert()
        .values({
          orderId,
          productId: product.id,
          price: product.price,
          quantity: product.quantity,
          tax: product.tax,
          discount: 0,
          createdAt: dto.createdAt || new Date(),
          updatedAt: new Date(),
        })
        .execute();

      // updating stock quantity
      await this.productService.updateStockQuantity(
        product.id,
        -1 * product.quantity,
        false,
      );
    }

    const user = await this.userService.findById(dto.userId);
    if (dto.orderStatusCode !== OrderStatus.pending) {
      await this.sms.sendNotifAddOrderToCustomer(
        user.mobile,
        user.surName || '-',
        orderNumber,
      );
    }

    const newOrder = await this.orderRepository.findOne(orderId);

    // log
    await this.logService.add({
      type: LogType.order,
      action: LogAction.insert,
      operatorUserId: sellerId,
      message: `سفارش به شماره #${newOrder.orderNumber} برای ${user.name} ${user.surName} ثبت شد.`,
      affectedId: String(newOrder.id),
      item: newOrder,
    });

    return newOrder;
  }

  /**
   * -------------------------------------------------------
   */
  async addByUser(
    user: UserEntity,
    dto: AddOrderByUserDto,
    customerIp: string = null,
  ) {
    if (dto.addressId) {
      const address = await this.addressService.getRecordById(
        dto.addressId,
        user.id,
      );
      if (!address) {
        this.error.methodNotAllowed(['شناسه آدرس وارد شده معتبر نمی‌باشد']);
      }
    }

    let car = null;
    if (dto.carId) {
      car = await this.carService.foundCarByUserId(dto.carId, user.id);
      if (!car) {
        this.error.methodNotAllowed(['شناسه خودروی وارد شده معتبر نمی‌باشد']);
      }
    }

    // force change to in persion for buying from shop and offline payment method
    if (dto.paymentMethod === OrderPaymentMethod.offline && !dto.isService) {
      dto.shippingType = OrderShippingType.inPerson;
    }

    // force disable pay by wallet in offline payment method state
    if (dto.paymentMethod === OrderPaymentMethod.offline) {
      dto.useWallet = false;
      dto.couponCode = null;
    }

    const {
      orderTotal,
      wageTotal,
      orderTax,
      orderShipping,
      couponDiscount,
      orderDiscount,
      vat,
      deductionsAmount,
      finalAmount,
      orderItems,
      usedWalletAmount,
      bankPayAmount,
    } = await this.calculateFinalAmount({
      ...dto,
      userId: user.id,
      makerBrandId: car?.makerBrandId || null,
      carId: dto?.carId || null,
    });

    const orderNumber = await this.generateOrderNumber();
    const date = dto.requestJalaliDate || moment().format('jYYYY/jMM/jDD');
    const time = dto.requestTime || moment().format('HH:mm');

    const payload: any = {
      ...dto,
      userId: user.id,
      orderNumber,
      clientType: dto.clientType || OrderClientType.site,
      date,
      time,
      orderGuid: uuidv4(),
      orderTotal,
      orderTax,
      vat,
      wageTotal,
      usedWalletAmount,
      orderShipping,
      orderDiscount,
      couponDiscount,
      deductionsAmount,
      finalAmount,
      customerIp,
      kilometers: dto?.kilometerNumber || null,
      paymentMethodSystemName: dto.paymentMethod,
      orderStatusCode: OrderStatus.pending,
      paymentStatusCode:
        bankPayAmount <= 0
          ? OrderPaymentStatus.paid
          : OrderPaymentStatus.pending,
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    delete payload.paymentGateway;
    const { identifiers } = await this.orderRepository
      .createQueryBuilder()
      .insert()
      .values(payload)
      .execute();

    const orderId = identifiers[0].id;

    // insert order items
    for (let i = 0; i < orderItems.length; i++) {
      const product = orderItems[i];

      await this.orderItemRepository
        .createQueryBuilder()
        .insert()
        .values({
          orderId,
          productId: product.id,
          price: product.price,
          quantity: product.quantity,
          wagePrice: product.wagePrice,
          tax: product.tax,
          discount: 0,
          createdAt: new Date(),
          updatedAt: new Date(),
        })
        .execute();

      // updating stock quantity
      await this.productService.updateStockQuantity(
        product.id,
        -1 * product.quantity,
        false,
      );
    }

    // reduce user wallet for complete order
    if (usedWalletAmount > 0 && bankPayAmount <= 0) {
      await this.userService.reduceBothWallets(user.id, usedWalletAmount);
    }

    // updating the car with new kilometer
    if (car && dto.kilometerNumber) {
      car.kilometerNumber = dto.kilometerNumber;
      await car.save();
    }

    const dateTime = `${date}ساعت${time}`;

    // Alert SMS to customer for the new service
    if (dto.isService) {
      await this.sms.notifyCustomerForNewService(
        user.mobile,
        user.surName || '-',
        dateTime,
        orderNumber,
      );
    }

    // Alert SMS to admin for the new order
    // await this.sms.notifyAdminForNewOrder('09125656429', orderNumber, dateTime);

    return { orderId, bankPayAmount };
  }

  /**
   * -------------------------------------------------------
   */
  async payByUser(user: UserEntity, dto: PayOrderByUserDto) {
    const order = await this.orderRepository.findOne({
      id: dto.orderId,
      userId: user.id,
      paymentStatusCode: OrderPaymentStatus.pending,
      orderStatusCode: OrderStatus.pending,
    });

    if (!order) {
      this.error.unprocessableEntity(['درخواست شما نامعتبر است']);
    }

    // Card to Card
    if (dto.cardToCardTrackingCode) {
      order.cardToCardTrackingCode = dto.cardToCardTrackingCode;
      order.paymentStatusCode = OrderPaymentStatus.cardToCard;
      order.updatedAt = new Date();
      await order.save();
      return { bankUrl: null, paid: true };
    }

    // Pay Online
    const bankPayAmount = order.finalAmount - (order.usedWalletAmount || 0);
    if (bankPayAmount === 0) {
      this.error.unprocessableEntity(['مبلغ قابل پرداخت صفر می‌باشد']);
    }

    // Creating the bank URL
    const { bankUrl } = await this.paymentService.createPayLink(
      bankPayAmount,
      order.usedWalletAmount,
      order.isService ? PaymentType.service : PaymentType.shop,
      dto.paymentGateway,
      order.id,
      user,
    );

    order.paymentGateway = OrderPaymentGateway?.[dto.paymentGateway] || null;
    order.updatedAt = new Date();
    await order.save();

    return { bankUrl, paid: false };
  }

  /**
   * -------------------------------------------------------
   */
  async paidByUser(orderId: number, snappPayPaymentToken: string = null) {
    const order = await this.orderRepository.findOne(orderId);

    if (!order) {
      this.error.unprocessableEntity(['سفارش شما یافت نشد']);
    }

    order.updatedAt = new Date();

    if (snappPayPaymentToken) {
      order.snappPayPaymentToken = snappPayPaymentToken;
      order.paymentStatusCode = OrderPaymentStatus.unsettledPaid; // پرداختِ تسویه نشده، در آخر ماه اتوتیک با اسنپ پی تسویه میکند
    } else {
      order.paymentStatusCode = OrderPaymentStatus.paid; // پرداخت شده
    }

    await order.save();

    return order;
  }

  /**
   * -------------------------------------------------------
   */
  async generateOrderNumber() {
    const orderNumber = 'AT' + moment(new Date()).format('jYYYYjMM'); // AT140101
    const date = moment(new Date()).format('jYYYY/jMM');

    const count = await this.orderRepository
      .createQueryBuilder('order')
      .where('SUBSTRING(date, 1, 7) = :date', { date })
      .getCount();

    return `${orderNumber}${count + 1}`;
  }

  /**
   * -------------------------------------------------------
   */
  async calVat(finalPrice) {
    const { percentVAT } = await this.siteInfoService.getInfo(['percentVAT']);

    let VAT = 0;
    if (percentVAT) {
      // 9% -> siteInfo.percentVAT
      VAT = Math.ceil((finalPrice * percentVAT) / 100);
    }

    return VAT;
  }

  /**
   * -------------------------------------------------------
   */
  async addService(dto: AddServiceDto) {
    const { makerBrandId } = await this.carService.getCarRecordById(dto.carId);

    const {
      orderItems,
      orderTotal,
      wageTotal,
      vat,
      deductionsAmount,
      orderShipping,
      finalAmount,
      orderDiscount,
      couponDiscount,
      orderTax,
    } = await this.calculateFinalAmount({
      ...dto,
      makerBrandId,
      isService: true,
      isNewOrder: true,
    });

    const orderNumber = await this.generateOrderNumber();

    const { identifiers } = await this.orderRepository
      .createQueryBuilder()
      .insert()
      .values({
        ...dto,
        orderNumber,
        isService: true,
        clientType: OrderClientType.panel,
        orderGuid: uuidv4(),
        orderTotal,
        orderTax,
        wageTotal,
        vat,
        orderDiscount,
        couponDiscount,
        orderShipping,
        deductionsAmount,
        finalAmount,
        paymentMethodSystemName: dto.paymentMethod,
        createdAt: dto.createdAt || new Date(),
        updatedAt: new Date(),
      })
      .execute();

    const orderId = identifiers[0].id;

    // insert order items
    for (let i = 0; i < orderItems.length; i++) {
      const product = orderItems[i];
      await this.orderItemRepository
        .createQueryBuilder()
        .insert()
        .values({
          orderId,
          productId: product.id,
          price: product.price,
          quantity: product.quantity,
          wagePrice: product.wagePrice,
          tax: product.tax,
          discount: 0,
          createdAt: dto.createdAt || new Date(),
          updatedAt: new Date(),
        })
        .execute();
    }

    const user = await this.userService.findById(dto.userId);
    const dateTime = `${dto.date} ساعت ${dto.time}`;

    if (dto.orderStatusCode !== OrderStatus.pending) {
      await this.sms.sendNotifAddServiceToCustomer(
        user.mobile,
        user.surName || '-',
        orderNumber,
        dateTime,
      );
    }

    return await this.orderRepository.findOne(orderId);
  }

  /**
   * -------------------------------------------------------
   */
  async generatePdf(): Promise<Buffer> {
    // Create a document
    const pdf = new PDF();

    const options = {
      width: 780,
      hideHeader: true,
      padding: 10,
      align: 'right',
      prepareRow: (row, indexColumn, indexRow, rectRow, rectCell) => {
        const { x, y, width, height } = rectCell;
        pdf.doc.font('Iransans').fontSize(8);

        if (indexRow === 0) {
          pdf.doc
            .lineWidth(0.5)
            .moveTo(rectRow.x, rectRow.y)
            .lineTo(rectRow.x + rectRow.width, rectRow.y)
            .stroke();
        }

        // first line
        if (indexColumn === 0) {
          pdf.doc
            .lineWidth(0.5)
            .moveTo(x, y)
            .lineTo(x, y + height)
            .stroke();
        }

        pdf.doc
          .lineWidth(0.5)
          .moveTo(x + width, y)
          .lineTo(x + width, y + height)
          .stroke();

        pdf.doc;
      },
      divider: {
        horizontal: { disabled: false, width: 0.5 }, // , opacity: 0.5
      },
    };

    await pdf.addTable(
      {
        headers: [
          { padding: 0, align: 'right' },
          { padding: 0 },
          { padding: 0 },
        ], // fix bug pdfkit-table
        rows: [
          ['روغن موتور پارس پایا 4L API SJ 20W50', 'روغن موتور', '123'],
          ['1', '2', '3'],
        ],
      },
      options,
    );

    pdf.addText(moment().locale('fa').format('jD jMMMM jYYYY'), 440, 173, 100);

    // Finalize PDF file
    return await pdf.endBuffer();
  }

  /**
   * -------------------------------------------------------
   * POST /orders/request-shipping-peyk
   */
  async requestShippingPeyk(dto: requestShippingDto, operatorUserId: string) {
    const order = await this.orderRepository.findOne({
      where: { id: dto.orderId },
      relations: [
        'address',
        'address.city',
        'user',
        'orderItems',
        'orderItems.product',
        'orderItems.product.category',
      ],
    });

    const { location } = await this.siteInfoService.getInfo(['location']);
    const [latOrigin, lngOrigin] = location.split(',');
    const [latDestination, lngDestination] = order.address.location.split(',');

    let result = { shippingMessage: 'پست' };

    if ([CityIds.tehran, CityIds.karaj].indexOf(order.address.cityId) !== -1) {
      // SUM QUANTITY OIL
      const sumOilQuantity = order.orderItems.reduce(
        (acc, orderItem) =>
          acc + (orderItem.product.categoryId === 1 ? orderItem.quantity : 0),
        0,
      );

      // alopeyk
      const res = await this.alopeykService.createOrder(
        latOrigin.trim(),
        lngOrigin.trim(),
        latDestination.trim(),
        lngDestination.trim(),
        `${dto.orderId}`,
        `${order.user.name || ''} ${order.user.surName || ''}`,
        order.user.mobile,
        sumOilQuantity > 2 ? 'car' : 'motorbike',
      );

      if (res.status === 'success') {
        order.shippingName = ShippingName.alopeyk;
        order.shippingOrderId = res.object.id;
        order.shippingOrderToken = res.object.order_token;
        await order.save();

        result.shippingMessage = 'پیک';
        result = { ...result, ...res.object };

        //log
        await this.logService.add({
          type: LogType.order,
          action: LogAction.insert,
          operatorUserId,
          message: `پیک برای سفارش به شماره #${order.orderNumber} ارسال شد.`,
          affectedId: String(order.id),
          item: order,
        });
      } else {
        console.log('result', res);
        this.error.internalServerError(['درخواست الوپیک با خطا مواجه شد']);
      }
    } else {
      // mahex
      const parcels = order.orderItems.map((orderItem) => ({
        id: `${order.orderNumber.substring(2)}${orderItem.id}`,
        weight: orderItem.quantity * (+orderItem.product.weight || 1),
        category: orderItem.product.category.name || 'روغن موتور',
      }));

      const res = await this.mahexService.createOrder(
        order.address.address,
        order.address.city.mahexCode,
        parcels,
        order.user.name,
        order.user.surName,
        order.user.mobile,
      );

      if (res?.status?.code === '201') {
        order.shippingName = ShippingName.mahex;
        order.shippingOrderId = res?.data?.shipment_uuid || '';
        await order.save();

        result.shippingMessage = 'پست';
        result = { ...result, ...res?.data };

        //log
        await this.logService.add({
          type: LogType.order,
          action: LogAction.insert,
          operatorUserId,
          message: `درخواست پستی ماهکس برای سفارش به شماره #${order.orderNumber} ارسال شد.`,
          affectedId: String(order.id),
          item: order,
        });
      } else {
        console.log('result', res);
        this.error.internalServerError(['درخواست ماهکس با خطا مواجه شد']);
      }
    }

    return result;
  }

  /**
   * -------------------------------------------------------
   * shipping-detail
   */
  async shippingDetail(orderId: number) {
    const order = await this.orderRepository.findOne(orderId);

    if (order.shippingName === ShippingName.alopeyk) {
      const result = await this.alopeykService.getDetail(order.shippingOrderId);
      if (result.status === 'success') {
        return result.object;
      } else {
        console.log(result);
        this.error.internalServerError(['خطایی در سمت الوپیک رخ داده است']);
      }
    } else if (order.shippingName === ShippingName.mahex) {
      const detailResult = await this.mahexService.getDetail(
        order.shippingOrderId,
      );

      if (detailResult?.status?.code === '200') {
        let trackResult = null;

        if (detailResult?.data?.waybill_number) {
          trackResult = await this.mahexService.getTrack(
            detailResult.data.waybill_number,
          );
          if (trackResult?.status?.code === '200') {
            trackResult = trackResult?.data;
          } else {
            trackResult = null;
          }
        }

        return { shipment: detailResult?.data, track: trackResult };
      } else {
        console.log(detailResult);
        this.error.internalServerError(['خطایی در سمت ماهکس رخ داده است']);
      }
    }

    this.error.internalServerError(['اطلاعات پستی یافت نشد']);
  }

  /**
   * -------------------------------------------------------
   * shipping-detail
   */
  async getAlopeykRequests(page = 1, perPage = 15) {
    const result = await this.alopeykService.getAll(page, perPage);

    if (result.status === 'success') {
      return {
        items: result.object.items,
        pagination: {
          itemsPerPage: result.object.perPage,
          totalItems: result.object.total,
          currentPage: result.object.page,
          totalPages: result.object.pages,
        },
      };
    } else {
      console.log('result', result);
      this.error.internalServerError(['خطایی در سمت الوپیک رخ داده است']);
    }
  }

  /**
   * -------------------------------------------------------
   * shipping-cancel
   */
  async cancelShipping(orderId: number, operatorUserId: string) {
    const order = await this.orderRepository.findOne(orderId);

    if (order.shippingName === ShippingName.alopeyk) {
      const result = await this.alopeykService.cancelOrder(
        order.shippingOrderId,
      );
      if (result.status !== 'success') {
        this.error.internalServerError(['خطایی در سمت الوپیک رخ داده است']);
      }

      // log
      await this.logService.add({
        type: LogType.order,
        action: LogAction.delete,
        operatorUserId,
        message: `ارسال پیک برای سفارش به شماره #${order.orderNumber} لغو شد.`,
        affectedId: String(order.id),
        item: order,
      });
      return true;
    }

    this.error.internalServerError(['اطلاعات پستی یافت نشد']);
  }

  /**
   * -------------------------------------------------------
   * get orders by date and isService=1
   */
  async getServicesByDate(date) {
    return await this.orderRepository
      .createQueryBuilder()
      .where({ date, isDeleted: 0, isService: 1 })
      .select(['date', 'time', 'COUNT(id) AS reservedTechnicianCount'])
      .groupBy('time')
      .getRawMany();
  }

  /**
   * -------------------------------------------------------
   * POST /orders/calculate-price
   */
  async calculatePriceForClient(
    dto: CalculatePriceForClientDto,
    userId: string,
  ) {
    if (dto.carId) {
      const { makerBrandId } = await this.carService.getCarRecordById(
        dto.carId,
      );
      dto.makerBrandId = makerBrandId;
    }

    const result = await this.calculateFinalAmount({
      ...dto,
      checkStock: dto.checkStock === false ? false : true,
      userId: dto.userId || userId,
    });

    // check snapp pay eligibility
    let snappPayEligibility = {
      success: false,
      title: null,
      description: null,
    };

    if (result.bankPayAmount) {
      try {
        const tempVat = result.vat ? 0 : (10 * result.bankPayAmount) / 100; // add 10% tax
        snappPayEligibility = await this.snappPayService.getEligibility(
          (result.bankPayAmount + tempVat) * 10,
        );
      } catch (e) {
        //
      }
    }

    // if (dto.paymentGateway === PaymentGateway.meli) {
    //   result.vat = (10 * result.bankPayAmount) / 100;
    //   result.bankPayAmount *= 1.1; // add 10% tax
    // }

    return {
      ...result,
      orderItems: result.orderItems.map((orderItem) => ({
        productUUID: orderItem.id,
        productId: orderItem.productId,
        quantity: orderItem.quantity,
        wagePrice: orderItem.wagePrice,
        tax: orderItem.tax,
        price: dto.isOilGovernment
          ? orderItem?.governmentPrice || orderItem.price
          : orderItem.price,
        priceInService: orderItem.priceInService,
        name: orderItem.name,
        image: orderItem.image,
        categoryId: orderItem.categoryId,
        shortDescription: orderItem.shortDescription,
        stockQuantity: orderItem.stockQuantity,
        governmentOilStockQuantity: orderItem.governmentOilStockQuantity,
        minStockQuantity: orderItem.minStockQuantity,
        orderMinimumQuantity: orderItem.orderMinimumQuantity,
        orderMaximumQuantity: orderItem.orderMaximumQuantity,
        weight: orderItem.weight,
        isOilGovernment: orderItem.isOilGovernment,
      })),
      snappPayEligibility,
    };
  }

  /**
   * productItems = [{productId, quantity}]
   */
  async calculateFinalAmount({
    productItems = [],
    isService = true,
    isNewOrder = true,
    isOilGovernment = false,
    checkStock = true,
    makerBrandId = null,
    addressId = null,
    shippingType = null,
    prevOrderShipping = 0, // It's for avoiding to send a new request to alopeyk/mahex
    adminDiscountPercent = 0,
    couponCode = null,
    useWallet = false,
    userId = null,
    carId = null,
    paymentGateway = null,
  }) {
    let allocation = 0;
    let user = null;
    if (userId) {
      user = await this.userService.findById(userId);

      try {
        ({ allocation } = await this.getGovernmentOilAllocation(userId, carId));
      } catch (e) {
        allocation = 0;
      }
    }

    // FIND PRODUCTS
    const products = await this.productService.getAllRawByIds(
      productItems.map((orderItem) => orderItem.productId),
    );

    // MERGE PRODUCT FIELDS WITH QUANTITY
    let orderItems = [];
    let addToOilLir = 0;
    productItems.forEach((productItem) => {
      const found = products.find(
        (product) => product.id === productItem.productId,
      );

      if (!found) {
        this.error.unprocessableEntity([`محصول مورد نظر یافت نشد`]);
      }

      if (found.categoryId === 1) {
        for (let i = 0; i < +productItem.quantity; i++) {
          let price = 0;
          let isOilGovernment = false;

          if (
            addToOilLir + (+found.weight || 0) <= allocation &&
            found.governmentOilStockQuantity > 0 &&
            found.governmentPrice > 0
          ) {
            price = found.governmentPrice;
            isOilGovernment = found.governmentPrice ? true : false;
            addToOilLir += +found.weight || 0;
          } else {
            price = found.price;
          }

          orderItems.push({
            ...found,
            orderItemId: productItem.id || null,
            quantity: 1,
            isNew: productItem?.isNew || false,
            wagePrice: productItem?.wagePrice || 0,
            tax: productItem?.tax || 0,
            price,
            isOilGovernment,
          });
        }
      } else {
        orderItems.push({
          ...found,
          orderItemId: productItem.id || null,
          quantity: productItem.quantity,
          isNew: productItem?.isNew || false,
          wagePrice: productItem?.wagePrice || 0,
          tax: productItem?.tax || 0,
          price: found.price,
          isOilGovernment: false,
        });
      }
    });

    orderItems = orderItems.reduce((result, pItem) => {
      if (pItem.categoryId === 1) {
        const existingItemIndex = result.findIndex(
          (item) => item.id === pItem.id && item.price === pItem.price,
        );
        if (existingItemIndex !== -1) {
          result[existingItemIndex].quantity += 1;
        } else {
          result.push({ ...pItem, quantity: 1 });
        }
      } else {
        result.push(pItem);
      }
      return result;
    }, []);

    // SUM QUANTITY OIL
    const sumOilQuantity = orderItems.reduce(
      (acc, orderItem) =>
        acc + (orderItem.categoryId === 1 ? orderItem.quantity : 0),
      0,
    );

    // SUM OIL LITR
    const totalOilLitr = orderItems.reduce(
      (acc, orderItem) =>
        acc +
        (orderItem.categoryId === 1
          ? (+orderItem.weight || 0) * orderItem.quantity
          : 0),
      0,
    );

    // SUM OIL PRICE
    const totalOilPrices = orderItems.reduce(
      (acc, orderItem) =>
        acc +
        (orderItem.categoryId === 1
          ? (orderItem.price || 0) * orderItem.quantity
          : 0),
      0,
    );

    // CHECKING STOCK
    if (checkStock) {
      orderItems.forEach((item) => {
        const isOil = item.categoryId === 1;

        // don't check stock for old items
        if (!isNewOrder && !item?.isNew) return;

        // check onlyOrderInService
        if (item.onlyOrderInService && !isService) {
          this.error.unprocessableEntity([
            `سفارش محصول ${item.name}، فقط از طریق بخش ثبت سرویس امکان پذیر است!`,
          ]);
        }

        // check orderMaximumQuantity
        if (item.orderMaximumQuantity < item.quantity) {
          this.error.unprocessableEntity([
            `حداکثر سفارش از محصول ${item.name}، تعداد  ${item.orderMaximumQuantity} می باشد.`,
          ]);
        }

        // check stockQuantity
        if (!isOil || (isOil && !isOilGovernment)) {
          if (item.stockQuantity < item.quantity) {
            let message = `حداکثر ${item.stockQuantity} مورد از محصول ${item.name} باقی مانده است.`;
            if (item.stockQuantity === 0) {
              message = `موجودی محصول ${item.name} به اتمام رسیده است`;
            }

            this.error.unprocessableEntity([message]);
          }
        } else {
          // check governmentOilStockQuantity
          if (item.governmentOilStockQuantity < item.quantity) {
            let message = `حداکثر ${
              item.governmentOilStockQuantity || 0
            } مورد از محصول ${item.name} باقی مانده است.`;
            if (item.governmentOilStockQuantity === 0) {
              message = `موجودی محصول ${item.name} به اتمام رسیده است`;
            }

            this.error.unprocessableEntity([message]);
          }
        }
      });
    }

    // ORDER TOTAL / WAGE
    const { orderTotal, wageTotal, orderTax, serviceDiscount } =
      await this.productService.calTotalPrice(
        orderItems,
        isService,
        makerBrandId,
      );

    // COUPON
    const couponDiscount = await this.discountService.calDiscountByCouponCode(
      couponCode,
      isService,
      orderTotal,
      wageTotal,
      isNewOrder,
      user,
    );

    // SHIPPING
    let orderShipping = prevOrderShipping || 0;

    if (
      !orderShipping &&
      addressId &&
      ((shippingType && !isService) || isService)
    ) {
      const address = await this.addressService.getRecordById(addressId);

      if (
        ((!isService && +shippingType === OrderShippingType.peyk) ||
          isService) &&
        [CityIds.tehran, CityIds.karaj].indexOf(address.cityId) !== -1
      ) {
        orderShipping = await this._calculateShippingByAlopeyk(
          isService,
          address,
          sumOilQuantity,
        );
      } else if (
        !isService &&
        +shippingType === OrderShippingType.post &&
        [CityIds.tehran, CityIds.karaj].indexOf(address.cityId) === -1
      ) {
        orderShipping = await this._calculateShippingByMahex(
          address,
          orderItems,
        );
      }
    }

    const deductionsAmount = 0;
    // if (isService && orderShipping) {
    //   deductionsAmount = orderShipping;
    // }

    // // VAT
    // const vat = await this.calVat(
    //   orderTotal + wageTotal - couponDiscount - serviceDiscount,
    // );

    // PRE FINAL AMOUNT
    const preFinalAmount =
      orderTotal +
      wageTotal +
      orderShipping +
      orderTax -
      couponDiscount -
      deductionsAmount -
      serviceDiscount;

    // CHECKING MIN PRICE
    if (checkStock) {
      const { minOrder } = await this.siteInfoService.getInfo(['minOrder']);

      if (!isService && preFinalAmount < minOrder) {
        this.error.unprocessableEntity([
          `ثبت سفارش کمتر از ${digit(minOrder)} تومان امکانپذیر نمی باشد`,
        ]);
      }
    }

    // ADMIN DISCOUNT
    const adminDiscount = (adminDiscountPercent * preFinalAmount) / 100;

    const orderDiscount = adminDiscount + couponDiscount + serviceDiscount; // total discount

    // FINAL AMOUNT
    let finalAmount = preFinalAmount - adminDiscount;

    // VAT
    const { percentVAT } = await this.siteInfoService.getInfo(['percentVAT']);
    let vatPercent = percentVAT || 0;
    let vatTotal = 0;
    if (
      paymentGateway === PaymentGateway.snappPay || // from dto
      paymentGateway === OrderPaymentGateway.snapp_pay // from order
    ) {
      vatPercent = 10;
    } else if (user?.customer) {
      vatPercent = user.customer.percentVAT || null;
    }
    if (vatPercent) {
      vatTotal = Math.ceil(finalAmount * vatPercent) / 100;
      finalAmount += vatTotal;
    }

    let bankPayAmount = finalAmount;
    let usedWalletAmount = 0;
    if (useWallet && userId) {
      const { realWallet, virtualWallet } = await this.userService.findById(
        userId,
      );
      const wallet = realWallet + virtualWallet;
      bankPayAmount -= wallet;

      if (bankPayAmount <= 0) {
        usedWalletAmount = finalAmount;
        bankPayAmount = 0;
      } else {
        usedWalletAmount = wallet;
      }
    }

    return {
      orderItems,
      orderTotal,
      wageTotal,
      orderShipping,
      shippingName: '',
      couponDiscount,
      adminDiscount,
      orderDiscount,
      serviceDiscount,
      vat: vatTotal,
      orderTax,
      deductionsAmount,
      finalAmount,
      usedWalletAmount,
      bankPayAmount,
      totalOilLitr,
      totalOilPrices,
      oilGovernmentAllocation: allocation,
    };
  }

  /**
   * -------------------------------------------------------
   * Alopeyk
   */
  private async _calculateShippingByAlopeyk(
    isService,
    address,
    sumOilQuantity,
  ) {
    const { location } = await this.siteInfoService.getInfo(['location']);
    const [latOrigin, lngOrigin] = location.split(',');

    const [latDestination, lngDestination] = address.location.split(',');

    const alopeykResult = await this.alopeykService.getPrice(
      latOrigin.trim(),
      lngOrigin.trim(),
      latDestination.trim(),
      lngDestination.trim(),
      sumOilQuantity > 2 || isService ? 'car' : 'motorbike',
    );

    let price = 0;
    if (alopeykResult.status === 'success') {
      const foundDestination = alopeykResult.object.addresses.find(
        (address) => address.type === 'destination',
      );

      if (
        !isService &&
        (moment().isBetween(
          moment('07:00:00', 'HH:mm:ss'),
          moment('10:00:00', 'HH:mm:ss'),
        ) ||
          moment().isBetween(
            moment('12:00:00', 'HH:mm:ss'),
            moment('13:30:00', 'HH:mm:ss'),
          ) ||
          moment().isBetween(
            moment('18:00:00', 'HH:mm:ss'),
            moment('20:30:00', 'HH:mm:ss'),
          ))
      ) {
        price = Math.round(Math.ceil(foundDestination.price * 1.4) / 10) * 10; // increase 40 percent
      } else {
        price = foundDestination.price;
      }
    } else {
      console.log('ALOPEYK STATUS FAILD', alopeykResult);
    }

    if (!price && !isService) {
      this.error.internalServerError([
        'خطایی در محاسبه هزینه پستی رخ داده است',
      ]);
    }

    return price > 0 ? price : 50000;
  }

  /**
   * -------------------------------------------------------
   * Mahex
   */
  private async _calculateShippingByMahex(address, orderItems) {
    if (!address.city.mahexCode) {
      this.error.internalServerError([
        'امکان ارسال مرسوله به شهر شما در حال حاضر وجود ندارد، لطفا با پشتیبانی 09391857423 تماس حاصل نمایید',
      ]);
    }

    const weights = orderItems.map(
      (orderItem) => orderItem.quantity * (+orderItem.weight || 1),
    );

    const mahexResult = await this.mahexService.getPrice(
      address.address,
      address.city.mahexCode,
      weights,
    );

    if (mahexResult?.status?.code === '200') {
      return parseInt(mahexResult?.data?.rate?.amount || '', 10) / 10; // Rial to Toman
    } else {
      console.log(
        'MAHEX STATUS FAILD',
        mahexResult,
        address.address,
        address.city.mahexCode,
        weights,
      );
      this.error.internalServerError([
        'امکان ارسال مرسوله به شهر شما در حال حاضر وجود ندارد، لطفا با پشتیبانی 09391857423 تماس حاصل نمایید',
      ]);
    }
  }

  /**
   * -------------------------------------------------------
   */
  async getGovernmentOilAllocation(user: UserEntity, carId: number = null) {
    // if (!user.mobile || !user.nationalCode) {
    //   this.error.unprocessableEntity(['کد ملی و شماره همراه اجباری می باشد.'], {
    //     code: null,
    //   });
    // }

    if (carId) {
      const foundCar = await this.carService.foundCarByUserId(carId, user.id);

      if (!foundCar) {
        this.error.unprocessableEntity(['خودروی مورد نظر یافت نشد'], {
          code: null,
        });
      }
    }

    const builder = this.orderRepository
      .createQueryBuilder()
      .andWhere({ userId: user.id })
      .andWhere({ isDeleted: false })
      .andWhere('governmentOilTrackingCode != ""')
      .andWhere('orderStatusCode != :canceled', {
        canceled: OrderStatus.canceled,
      })
      .andWhere('createdAt >= :orderOneMonthAgo', {
        orderOneMonthAgo: moment().subtract(1, 'months').format('YYYY-MM-DD'),
      })
      .orderBy('createdAt', 'DESC');

    if (carId) {
      builder.andWhere({ carId });
    }

    const lastOrder = await builder.getOne();
    console.log(lastOrder);

    if (lastOrder) {
      if (lastOrder.paymentStatusCode === OrderPaymentStatus.pending) {
        this.error.unprocessableEntity(
          [
            'شما یک سفارش پرداخت نشده دارید، لطفا به بخش فاکتور‌ها در پروفایل خود مراجعه کرده و وضعیت آن را مشخص نمایید',
          ],
          {
            code: null,
          },
        );
      } else if (
        lastOrder.paymentStatusCode === OrderPaymentStatus.cardToCard ||
        lastOrder.paymentStatusCode === OrderPaymentStatus.paid
      ) {
        this.error.unprocessableEntity(
          ['ظرفیت روغن دولتی خودروی شما برای ماه جاری به پایان رسیده است'],
          {
            code: null,
          },
        );
      }
    }

    // if (!foundCar.vinCode || !foundCar.engineCode) {
    //   this.error.unprocessableEntity(
    //     ['شماره شاسی و موتور خودرو،اجباری می باشد.'],
    //     { code: null },
    //   );
    // }

    // const cleanMobile =
    //   user.mobile.substring(0, 1) === '0'
    //     ? user.mobile.substring(1)
    //     : user.mobile;

    // try {
    //   return await this.samtService.GetOilAllocation(
    //     { mobile: cleanMobile, nationalCode: user.nationalCode },
    //     { vinCode: foundCar.vinCode, engineCode: foundCar.engineCode },
    //   );
    // } catch (err) {
    //   if (err?.code === 10001) {
    //     const allocation =
    //       await this.carService.getOilGovernmentAllocatedByLastChecked(carId);

    //     if (allocation) {
    //       return { allocation, fleetType: 0 };
    //     }
    //   }

    //   this.error.internalServerError([err.message], { code: err.code });
    // }

    return { allocation: 8, fleetType: 0 };
  }

  /**
   * -------------------------------------------------------
   */
  async getAndSetGovernmentOilAllocationByAdmin(orderId: number) {
    const order = await this.orderRepository.findOne({
      where: { id: orderId },
      relations: ['user', 'car', 'orderItems', 'orderItems.product'],
    });

    if (!order || !order?.user || !order?.car) {
      this.error.unprocessableEntity(['درخواست نامعتبر است']);
    }

    const { allocation }: any = await this.getGovernmentOilAllocation(
      order.user,
      order.carId,
    );

    const sumOil = order.orderItems.reduce(
      (acc, item) => {
        // oil
        if (item.product.categoryId === 1) {
          return {
            litr: acc.litr + (+item.product.weight || 0) * item.quantity,
            price: acc.price + item.product.price * item.quantity,
          };
        }
        return acc;
      },
      { litr: 0, price: 0 },
    );

    if (allocation === 0) {
      this.error.unprocessableEntity([
        'ظرفیت روغن دولتی مشتری مورد نظر به اتمام رسیده است',
      ]);
    }

    if (sumOil.litr > allocation) {
      this.error.unprocessableEntity([
        `روغن موتور درخواستی مشتری بیش از ظرفیت بررسی شده می‌باشد، حداکثر ظرفیت ${allocation} لیر می‌باشد`,
      ]);
    }

    const { trackingCode }: any = await this.governmentOilSaleOrder(
      order.user,
      {
        carId: order.carId,
        litr: sumOil.litr,
        price: sumOil.price,
      },
    );

    order.governmentOilTrackingCode = trackingCode;
    await order.save();
  }

  /**
   * -------------------------------------------------------
   */
  async governmentOilSaleOrder(
    user: UserEntity,
    dto: GovernmentOilSaleOrderDto,
  ) {
    // if (!user.mobile || !user.nationalCode) {
    //   this.error.unprocessableEntity(['کد ملی و شماره همراه اجباری می باشد.'], {
    //     code: null,
    //   });
    // }

    const foundCar = await this.carService.foundCarByUserId(dto.carId, user.id);

    if (!foundCar) {
      this.error.unprocessableEntity(['خودروی مورد نظر یافت نشد'], {
        code: null,
      });
    }

    // if (!foundCar.vinCode || !foundCar.engineCode) {
    //   this.error.unprocessableEntity(
    //     ['شماره شاسی و موتور خودرو،اجباری می باشد.'],
    //     { code: null },
    //   );
    // }

    // const cleanMobile =
    //   user.mobile.substring(0, 1) === '0'
    //     ? user.mobile.substring(1)
    //     : user.mobile;

    // try {
    //   return await this.samtService.GetOilSaleOrder(
    //     { mobile: cleanMobile, nationalCode: user.nationalCode },
    //     { vinCode: foundCar.vinCode, engineCode: foundCar.engineCode },
    //     { code: '2902348201638', price: dto.price, litr: dto.litr },
    //   );
    // } catch (err) {
    //   this.error.internalServerError([err.message], { code: err.code });
    // }

    return { trackingCode: '-1' };
  }

  /**
   * -------------------------------------------------------
   */
  async getRemainDaysToNewGovernmentOil(user: UserEntity, carId: number) {
    if (!carId || !user.nationalCode) {
      this.error.unprocessableEntity([
        'ورود کد ملی و شماره پلاک خودرو اجباری می باشد.',
      ]);
    }

    const foundCar = await this.carService.foundCarByUserId(carId, user.id);

    if (!foundCar) {
      this.error.unprocessableEntity(['خودروی مورد نظر یافت نشد']);
    }

    const lastOrder = await this.orderRepository
      .createQueryBuilder()
      .andWhere({ carId })
      .andWhere({ isDeleted: false })
      .andWhere('governmentOilTrackingCode != ""')
      .andWhere('orderStatusCode != :canceled', {
        canceled: OrderStatus.canceled,
      })
      .andWhere('createdAt >= :orderThreeMonthAgo', {
        orderThreeMonthAgo: moment().subtract(3, 'months').format('YYYY-MM-DD'),
      })
      .orderBy('createdAt', 'DESC')
      .getOne();

    if (
      lastOrder?.paymentStatusCode === OrderPaymentStatus.cardToCard ||
      lastOrder?.paymentStatusCode === OrderPaymentStatus.paid
    ) {
      return {
        remainDays: 90 - moment().diff(moment(lastOrder.createdAt), 'days'),
        allocation: 8,
      };
    }

    return {
      remainDays: 0,
      allocation: 8,
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getRemainDaysToNewService(user: UserEntity, carId: number) {
    if (!carId) {
      this.error.unprocessableEntity(['ورود شماره پلاک خودرو اجباری می باشد.']);
    }

    const orderDeadlineKilometer = await this.orderRepository
      .createQueryBuilder('order')
      .leftJoinAndMapOne(
        'order.relatedOrder',
        OrderEntity,
        'related',
        'related.carId = order.carId AND related.isService = 1 AND related.createdAt > order.createdAt',
      )
      .andWhere({ isService: true, carId, userId: user.id, isDeleted: false })
      .andWhere('order.orderStatusCode != :pending', {
        pending: OrderStatus.pending,
      })
      .andWhere('order.orderStatusCode != :canceled', {
        canceled: OrderStatus.canceled,
      })
      .andWhere('order.minKilometersDay IS NOT NULL')
      .andWhere(
        '(DATEDIFF(CURDATE(), order.createdAt) * (order.minKilometersDay + IFNULL(order.maxKilometersDay, 90)) / 2 > :deadlineKilometer OR DATEDIFF(CURDATE(), order.createdAt) > :deadlineDay)',
        { deadlineKilometer: 5000, deadlineDay: 180 },
      )
      .andWhere('related.id IS NULL')
      .orderBy('order.createdAt', 'DESC')
      .getOne();

    if (orderDeadlineKilometer) {
      return { remainDays: 0 };
    }

    const lastOrder = await this.orderRepository
      .createQueryBuilder()
      .andWhere({ isService: true, carId, userId: user.id, isDeleted: false })
      .andWhere('orderStatusCode != :pending', {
        pending: OrderStatus.pending,
      })
      .andWhere('orderStatusCode != :canceled', {
        canceled: OrderStatus.canceled,
      })
      .andWhere('createdAt >= :orderSixMonthAgo', {
        orderSixMonthAgo: moment().subtract(6, 'months').format('YYYY-MM-DD'),
      })
      .orderBy('createdAt', 'DESC')
      .getOne();

    if (lastOrder) {
      const remainDays =
        180 - moment().diff(moment(lastOrder.createdAt), 'days');
      return { remainDays: remainDays < 0 ? 0 : remainDays };
    }

    return { remainDays: 0 };
  }

  /**
   * -------------------------------------------------------
   */
  async prepareSnappPayCartList(orderId: number) {
    const order = await this.orderRepository.findOne({
      where: { id: orderId },
      relations: [
        'orderItems',
        'orderItems.product',
        'orderItems.product.category',
      ],
    });

    const itemsAmount = order.orderItems.reduce(
      (acc, item) => acc + item.quantity * item.price,
      0,
    );
    const shippingAmount = (order.orderShipping || 0) + (order.wageTotal || 0);
    const discountAmount = order.orderDiscount || 0;
    const taxAmount = (order.vat || 0) + (order.orderTax || 0);
    const totalAmount = itemsAmount + shippingAmount + taxAmount;

    return {
      cartList: [
        {
          cartId: order.id,
          cartItems: order.orderItems.map((orderItem) => ({
            amount: orderItem.price * 10,
            category: orderItem.product.category.name,
            count: orderItem.quantity,
            id: orderItem.id,
            name: orderItem.product.name,
            commissionType: 100,
          })),
          totalAmount: totalAmount * 10,
          isShipmentIncluded: !!shippingAmount,
          isTaxIncluded: true,
          shippingAmount: shippingAmount * 10,
          taxAmount: taxAmount * 10,
        },
      ],
      finalAmount: (totalAmount - discountAmount) * 10,
      discountAmount: discountAmount * 10,
      externalSourceAmount: 0,
      shippingAmount: shippingAmount * 10,
    };
  }

  /**
   * -------------------------------------------------------
   */
  async sendSshafList(dto: SshafDto) {
    try {
      return await this.sshafService.setOil(
        dto.roleCode,
        dto.postalCode,
        dto.sellerPhone,
        dto.sellerNationalCode,
        dto.items,
      );
    } catch (e) {
      this.error.methodNotAllowed([e.message], e.data);
    }
  }

  /**
   * -------------------------------------------------------
   */
  async sendSamtItem(dto: SamtDto) {
    const cleanMobile =
      dto.mobile.substring(0, 1) === '0' ? dto.mobile.substring(1) : dto.mobile;

    try {
      return await this.samtService.GetOilSaleOrder(
        { mobile: cleanMobile, nationalCode: dto.nationalCode },
        { vinCode: dto.vinCode, engineCode: dto.engineCode },
        { code: '2902348201638', price: dto.price, litr: dto.litr },
      );
    } catch (e) {
      this.error.methodNotAllowed([e.message], { code: e.code });
    }
  }
}
