import { Injectable } from '@nestjs/common';
import * as moment from 'jalali-moment';
import * as tmp from 'tmp';
import { ErrorService } from '../../error/error.service';
import { SmsService } from '../../sms/sms.service';
import { OrderService } from '../order/order.service';
import {
  ServiceReminderEntity,
  ServiceReminderStatus,
} from './entities/service-reminder.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
} from '../../utils';
import { Workbook } from 'exceljs';
import { ServiceTimeService } from '../service-time/service-time.service';
import { ServiceDayClosedService } from '../service-day-closed/service-day-closed.service';
import { LogService } from '../log/log.service';
import { LogAction, LogType } from '../log/log.interface';
import { LogClient } from '../log/entities/log.entity';
import { SiteInfoService } from '../siteInfo/site-info.service';
import { UpdateWeekClosedDto } from './dto/update-week-closed.dto';

@Injectable()
export class ServiceService {
  constructor(
    @InjectRepository(ServiceReminderEntity)
    private serviceReminderRepository: Repository<ServiceReminderEntity>,

    private orderService: OrderService,
    private serviceTimeService: ServiceTimeService,
    private serviceDayClosedService: ServiceDayClosedService,
    private siteInfoService: SiteInfoService,
    private error: ErrorService,
    private sms: SmsService,
    private logService: LogService,
  ) {}

  /**
   * -------------------------------------------------------
   * Getting times list for admin
   */
  async getTimesByAdmin() {
    return await this.serviceTimeService.findAll();
  }

  /**
   * -------------------------------------------------------
   * Getting available times list base on date
   */
  async getTimesAvailable(date, isService = true) {
    const selectedDayMoment = moment(date.replace(/\//g, '-'));

    // week closed
    const weekClosed = await this.getWeekClosed(true);
    const maxNumberServiceInTime = +weekClosed.maxNumberServiceInTime;
    const weekClosedArr = [
      weekClosed.mondayServiceClosed,
      weekClosed.tuesdayServiceClosed,
      weekClosed.wednesdayServiceClosed,
      weekClosed.thursdayServiceClosed,
      weekClosed.fridayServiceClosed,
      weekClosed.saturdayServiceClosed,
      weekClosed.sundayServiceClosed,
    ];

    if (weekClosedArr[selectedDayMoment.day() - 1]) {
      this.error.methodNotAllowed(['تاریخ مورد نظر تعطیل می‌باشد!']);
    }

    const times = await this.serviceTimeService.findAll({ isActive: true });

    // day closed
    const foundClosedDay = await this.serviceDayClosedService.getByDate(date);
    if (foundClosedDay) {
      this.error.methodNotAllowed([
        `روز مورد نظر ${
          foundClosedDay.description
            ? `به دلیل ${foundClosedDay.description} `
            : ''
        }تعطیل می‌باشد`,
      ]);
    }

    // check today
    let availableTimes: any = times;
    const isToday = selectedDayMoment.isSame(new Date(), 'day');

    if (isToday) {
      availableTimes = times.filter((time) => {
        const startTime = time.timeRange.split('-')[0];

        return moment(
          `${moment().format('YYYY-MM-DD')} ${startTime}:00:00`,
        ).isAfter(moment().add(2, 'hour'));
      });
    }

    if (availableTimes.length === 0) {
      this.error.methodNotAllowed([
        'ساعت کاری روز مورد نظر به اتمام رسیده است، لطفا روز دیگری را انتخاب نمایید',
      ]);
    }

    if (isService) {
      const services = await this.orderService.getServicesByDate(
        selectedDayMoment.format('jYYYY/jMM/jDD'),
      );

      availableTimes = availableTimes.map((availableTime) => {
        let isActive = true;

        const found = services.find((s) => s.time === availableTime.timeRange);

        if (found && +found.reservedTechnicianCount >= maxNumberServiceInTime) {
          isActive = false;
        }

        return { ...availableTime, isActive };
      });

      if (availableTimes.length === 0) {
        this.error.methodNotAllowed([
          'تمام ساعات سرویس‌دهی روز مورد نظر رزرو شده است، لطفا روز دیگری را انتخاب نمایید',
        ]);
      }
    }

    return availableTimes;
  }

  /**
   * -------------------------------------------------------
   */
  async getAllReminders(page = 1, limit = 20, filters = null, sorts = null) {
    const builder = await this._builder(filters, sorts);

    builder.take(limit);
    builder.skip((page - 1) * limit);

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

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

  /**
   * -------------------------------------------------------
   */
  async getReminderById(id: number) {
    return await this.serviceReminderRepository
      .createQueryBuilder('reminder')
      .leftJoinAndSelect('reminder.user', 'user')
      .leftJoinAndSelect('reminder.prevOrder', 'prevOrder')
      .where({ id })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async updateReminderById(id, data) {
    return await this.serviceReminderRepository
      .createQueryBuilder()
      .update()
      .set(data)
      .where({ id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  async handleDeadline() {
    const list = await this._getAllhandleDeadline();

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

      // Adding reminder
      if (item.serviceReminders.length === 0) {
        const newReminder = await this._addNewReminder(
          item.id,
          item.user.id,
          item.deadlineKilometer,
        );

        // Adding a log
        await this.logService.add({
          type: LogType.reminder,
          action: LogAction.insert,
          client: LogClient.system,
          message: `نوبت سرویس دوره ای خودروی ${
            item.car.model || item.car.makerBrand?.name || ''
          } به شماره انتظامی ${item.car.number.split('***').join('-')} کاربر ${
            item.user.name
          } ${item.user.surName} فرا رسیده است.`,
          affectedId: String(newReminder.id),
          item: newReminder,
        });
      } else {
        // Prevent sending SMS
        if (
          item.serviceReminders[0].lastSmsKilometer === item.deadlineKilometer
        ) {
          continue;
        }
        // Updating lastSmsKilometer in the reminder table
        else {
          await this.updateReminderById(item.serviceReminders[0].id, {
            lastSmsKilometer: item.deadlineKilometer,
          });
        }
      }

      // Sending SMS
      await this.sms.sendServiceReminder(
        item.user.mobile,
        `${item.user.name} ${item.user.surName}`.trim() || '-',
        item.car.model || item.car.makerBrand?.name || '',
        item.car.number,
      );
    }
  }

  /**
   * -------------------------------------------------------
   */
  private async _addNewReminder(
    prevOrderId: number,
    userId: string,
    lastSmsKilometer = null,
  ) {
    const { identifiers } = await this.serviceReminderRepository
      .createQueryBuilder()
      .insert()
      .values({
        prevOrderId,
        userId,
        lastSmsKilometer,
        createdAt: new Date(),
        status: ServiceReminderStatus.new,
      })
      .execute();

    const newReminderId = identifiers[0].id;
    return await this.serviceReminderRepository.findOne(newReminderId);
  }

  /**
   * -------------------------------------------------------
   */
  private async _getAllhandleDeadline() {
    const kilometersList = [5200, 5000, 4800];
    const finalList = [];

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

      const list = await this.orderService.findAllDeadlineService(
        kilometer,
        180,
      );
      list.forEach((item) => {
        if (finalList.find((f) => f.id === item.id)) return;
        finalList.push({ ...item, deadlineKilometer: kilometer });
      });
    }

    return finalList;
  }

  /**
   * -------------------------------------------------------
   */
  async exportExcel(filters = null) {
    const items = await this._builder(filters).getMany();

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

    worksheet.addRow([
      'مشتری',
      'موبایل',
      'مدل خودرو',
      'پلاک خودرو',
      'آخرین کیلومتر',
      'تاریخ آخرین سرویس',
      'وضعیت',
      'تاریخ ثبت گزارش',
    ]);

    items.forEach((item) => {
      const user = item.user;
      const prevOrder = item.prevOrder;
      const car = prevOrder.car;

      worksheet.addRow([
        `${user.name} ${user.surName}`,
        user.mobile,
        car?.model || car?.makerBrand?.name || '-',
        car?.number ? car.number.split('***').join(' ') : '-',
        prevOrder.kilometers || prevOrder.car?.kilometerNumber,
        moment(prevOrder.createdAt).format('jYYYY-jMM-jDD'),
        item.status,
        moment(item.createdAt).format('jYYYY-jMM-jDD'),
      ]);
    });

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

    worksheet.getColumn(1).width = 20;
    worksheet.getColumn(2).width = 13;
    worksheet.getColumn(3).width = 20;
    worksheet.getColumn(4).width = 17;
    worksheet.getColumn(5).width = 12;
    worksheet.getColumn(6).width = 16;
    worksheet.getColumn(7).width = 8;
    worksheet.getColumn(8).width = 13;

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

  private _builder(filters, sorts = null) {
    let builder = this.serviceReminderRepository
      .createQueryBuilder('reminder')
      .leftJoin('reminder.user', 'user')
      .leftJoin('reminder.prevOrder', 'order')
      .leftJoin('order.car', 'car')
      .leftJoin('car.makerBrand', 'makerBrand')

      .select([
        'reminder.id',
        'reminder.lastSmsKilometer',
        'reminder.status',
        'reminder.message',
        'reminder.createdAt',

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

        'order.id',
        'order.createdAt',
        'order.kilometers',

        'car.number',
        'car.model',
        'car.kilometerNumber',

        'makerBrand.id',
        'makerBrand.name',
      ]);

    builder = applyFiltersToBuilder(builder, filters);

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

    return builder;
  }

  /**
   * -------------------------------------------------------
   */
  async getWeekClosed(getServiceInTimeCount = false) {
    return await this.siteInfoService.getInfo([
      'saturdayServiceClosed',
      'sundayServiceClosed',
      'mondayServiceClosed',
      'tuesdayServiceClosed',
      'wednesdayServiceClosed',
      'thursdayServiceClosed',
      'fridayServiceClosed',
      ...(getServiceInTimeCount ? ['maxNumberServiceInTime'] : []),
    ]);
  }

  /**
   * -------------------------------------------------------
   */
  async updateWeekClosed(dto: UpdateWeekClosedDto, operatorUserId: string) {
    const oldItem = await this.getWeekClosed();

    await this.siteInfoService.update(dto);

    // Adding a log
    await this.logService.add({
      type: LogType.service_time,
      action: LogAction.update,
      operatorUserId,
      message: `جزئیات سرویس دهی ویرایش شد.`,
      // affectedId: String(oldItem.id),
      oldItem,
      item: dto,
    });

    return true;
  }
}
