import { Injectable } from '@nestjs/common';
import { Workbook } from 'exceljs';
import * as moment from 'jalali-moment';
import * as tmp from 'tmp';
import { InjectRepository } from '@nestjs/typeorm';
import { ErrorService } from '../../error/error.service';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  currency,
  paginationResult,
} from '../../utils';
import { Repository } from 'typeorm';
import { DiscountEntity, AvailableSection } from './entities/discount.entity';
import { CreateDiscountDto } from './dto/create-discount.dto';
import { UpdateDiscountDto } from './dto/update-discount.dto';
import { UserEntity } from '../user/entities/user.entity';

@Injectable()
export class DiscountService {
  constructor(
    @InjectRepository(DiscountEntity)
    private discountRepository: Repository<DiscountEntity>,
    private error: ErrorService,
  ) {}

  /**
   * -------------------------------------------------------
   */
  async getAll(page = 1, limit = 20, filters = null, sorts = null) {
    let builder = this.discountRepository
      .createQueryBuilder('discount')
      .leftJoinAndSelect('discount.customer', 'customer')
      .leftJoin('discount.user', 'user')
      .addSelect(['user.name', 'user.name', 'user.surName', 'user.mobile'])
      .take(limit)
      .skip((page - 1) * limit);

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

    builder = applyFiltersToBuilder(builder, filters);

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

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

  /**
   * -------------------------------------------------------
   */
  async getById(id: number) {
    return await this.discountRepository
      .createQueryBuilder('discount')
      .where({ id })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async statistic() {
    const serviceSection = await this.discountRepository.count({
      section: AvailableSection.service,
    });
    const productSection = await this.discountRepository.count({
      section: AvailableSection.product,
    });
    const bothSection = await this.discountRepository.count({
      section: AvailableSection.both,
    });

    const total = await this.discountRepository.count();
    const used = await this.discountRepository
      .createQueryBuilder('discount')
      .innerJoin('discount.order', 'order')
      .getCount();

    return {
      used,
      unUsed: total - used,
      total,
      service: serviceSection,
      product: productSection,
      both: bothSection,
    };
  }

  /**
   * -------------------------------------------------------
   */
  async exportExcel(filters = null) {
    let builder = this.discountRepository
      .createQueryBuilder('discount')
      .leftJoinAndSelect('discount.customer', 'customer')
      .leftJoinAndSelect('discount.user', 'user');

    builder.orderBy('discount.createdAt', 'DESC');
    builder = applyFiltersToBuilder(builder, filters);
    const items = await builder.getMany();

    // Create excel file
    const workbook = new Workbook();
    const worksheet = workbook.addWorksheet(`sheet1`);
    worksheet.views = [{ rightToLeft: true }];

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

    items.forEach((item) => {
      worksheet.addRow([
        item.name,
        item.customer?.name || '-',
        item.user ? `${item.user.name} ${item.user.surName}` : '-',
        item.couponCode,
        item.discountPercentage ? `${item.discountPercentage}%` : '',
        item.discountAmount ? currency(item.discountAmount) : '',
        item.endDate ? moment(item.endDate).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;
    worksheet.getColumn(7).width = 15;

    // Save on tmp and export excel file
    try {
      const tmpobj = tmp.fileSync({
        mode: 0o644,
        prefix: `discount_${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(['در تولید فایل اکسل خطایی رخ داده است']);
    }
  }

  /**
   * -------------------------------------------------------
   */
  async create(dto: CreateDiscountDto) {
    const generatedDiscounts = [];

    for (let i = 1; i <= dto.countCouponCode; i++) {
      const newDiscount = new DiscountEntity();
      for (const key in dto) {
        newDiscount[key] = dto[key];
      }
      newDiscount.requiresCouponCode = true;
      newDiscount.usePercentage = !!dto.discountPercentage;
      newDiscount.createdAt = new Date();
      newDiscount.updatedAt = new Date();
      newDiscount.customerId = dto.customerId || null;

      newDiscount.couponCode =
        dto.countCouponCode === 1 && dto.couponCode
          ? dto.couponCode
          : await this._generateUniqueCouponCode();

      await newDiscount.save();
      generatedDiscounts.push(newDiscount);
    }

    return generatedDiscounts;
  }

  /**
   * -------------------------------------------------------
   */
  async updateById(dto: UpdateDiscountDto) {
    const discount = await this.discountRepository.findOne(dto.id);
    for (const key in dto) {
      discount[key] = dto[key];
    }
    discount.customerId = dto.customerId || null;

    discount.usePercentage = !!dto.discountPercentage;
    if (discount.usePercentage) {
      discount.discountAmount = null;
    } else {
      discount.discountPercentage = null;
    }

    discount.updatedAt = new Date();

    this.discountRepository
      .createQueryBuilder()
      .update()
      .set(discount)
      .where({ id: dto.id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  private async _generateUniqueCouponCode(length = 7) {
    let code;
    const characters =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    do {
      code = '';
      for (let i = 0; i < length; i++) {
        code += characters.charAt(
          Math.floor(Math.random() * characters.length),
        );
      }
    } while (await this._existByCouponCode(code));

    return code;
  }

  /**
   * -------------------------------------------------------
   */
  private async _existByCouponCode(couponCode: string) {
    const count = await this.discountRepository.count({ couponCode });
    return count > 0;
  }

  /**
   * --------------------------------------------------------
   */
  async calDiscountByCouponCode(
    code: string,
    isService = true,
    orderTotal = 0, // price * quantity
    totalWage = 0,
    runValidations = true,
    user: UserEntity = null,
  ) {
    // product | service | both
    const section = isService ? 'service' : 'product';

    if (!code) return 0;

    const builder = this.discountRepository
      .createQueryBuilder('discount')
      .andWhere({ couponCode: code });

    if (runValidations) {
      // Not already used
      builder.leftJoin('discount.order', 'order').andWhere('order.id IS NULL');
    }

    const coupon = await builder.getOne();

    if (!coupon) return 0;

    if (runValidations) {
      const dateNow = new Date();
      if (coupon.startDate && coupon.startDate > dateNow) {
        return 0;
      }

      if (coupon.endDate && coupon.endDate < dateNow) {
        return 0;
      }

      if (coupon.section !== 'both' && coupon.section !== section) {
        return 0;
      }

      if (
        user?.customerId &&
        coupon.customerId &&
        coupon.customerId !== user.customerId
      ) {
        return 0;
      }

      if (user && coupon.userId && coupon.userId !== user.id) {
        return 0;
      }
    }

    if (!coupon.usePercentage) {
      return coupon.discountAmount;
    }

    if (section === 'service') {
      return Math.ceil((totalWage * coupon.discountPercentage) / 100);
      // return Math.ceil(((amount + totalWage) * coupon.discountPercentage) / 100);
    }

    return Math.ceil((orderTotal * coupon.discountPercentage) / 100);
  }

  /**
   * -------------------------------------------------------
   * DELETE /discount/1
   */
  async deleteDiscount(id: number) {
    return await this.discountRepository
      .createQueryBuilder()
      .update()
      .set({ deleted: true, updatedAt: new Date() })
      .where({ id })
      .execute();
  }
}
