import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
  sleep,
} from '../../utils';
import * as moment from 'jalali-moment';

import { MoreThanOrEqual, Repository } from 'typeorm';
import { CarEntity } from './entities/car.entity';
import { MakerEntity } from './entities/maker.entity';
import { MakerBrandEntity } from './entities/maker-brand.entity';
import { CreateCarDto } from './dto/create-car.dto';
import { UpdateCarDto } from './dto/update-car.dto';
import { CreateMakerBrandDto } from './dto/create-maker-brand.dto';
import { UpdateMakerBrandDto } from './dto/update-maker-brand.dto';
import { CreateMakerDto } from './dto/create-maker.dto';
import { UpdateMakerDto } from './dto/update-maker.dto';
import { MakerBrandProductDto } from './dto/maker-brand-product.dto';
import { ProductService } from '../product/product.service';
import { getLastDisplayOrder } from 'src/utils/last-display-order';
import { LogService } from '../log/log.service';
import { LogAction, LogType } from '../log/log.interface';
import { SmsService } from 'src/sms/sms.service';

@Injectable()
export class CarService {
  constructor(
    @InjectRepository(CarEntity)
    private carRepository: Repository<CarEntity>,

    @InjectRepository(MakerEntity)
    private makerRepository: Repository<MakerEntity>,

    @InjectRepository(MakerBrandEntity)
    private makerBrandRepository: Repository<MakerBrandEntity>,

    private productService: ProductService,
    private logService: LogService,

    private sms: SmsService,
  ) {}

  /**
   * -------------------------------------------------------
   */
  async getCarById(id: number, userId = null) {
    const builder = this._findBuilder(userId ? false : true);
    builder.andWhere({ id });

    if (userId) {
      builder.andWhere({ userId, deleted: false });
    }

    const item = await builder.getOne();
    return item;
  }

  /**
   * -------------------------------------------------------
   */
  async getCarRecordById(id: number, userId = null) {
    const where: any = { id };
    if (userId) {
      where.userId = userId;
    }
    return await this.carRepository.findOne(where);
  }

  /**
   * -------------------------------------------------------
   */
  private _findBuilder(fullDetails = true) {
    const builder = this.carRepository.createQueryBuilder('car');

    builder.leftJoin('car.makerBrand', 'makerBrand');
    builder.leftJoin('makerBrand.maker', 'maker');

    builder.select([
      'car.id',
      'car.carName',
      'car.model',
      'car.type',
      'car.number',
      'car.vinCode',
      'car.engineCode',
      'car.kilometerNumber',
      'car.createdAt',

      'makerBrand.id',
      'makerBrand.name',
      'makerBrand.image',
      'makerBrand.oilUseWithFilter',
      'makerBrand.oilUseWithoutFilter',
      'makerBrand.oilTechnical',

      'maker.id',
      'maker.name',
      'maker.image',
    ]);

    if (fullDetails) {
      builder.leftJoin('car.user', 'user');
      builder.leftJoin('car.orders', 'orders');
      builder.addSelect([
        'orders.id',
        'orders.kilometers',
        'orders.createdAt',
        'user.id',
        'user.name',
        'user.surName',
      ]);
    }

    return builder;
  }
  /**
   * -------------------------------------------------------
   */
  async carsList(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    userId = null,
  ) {
    let builder = this._findBuilder(userId ? false : true);

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

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

    builder = applyFiltersToBuilder(builder, filters);

    // Custom condition
    if (userId) {
      builder.andWhere({ userId, deleted: false });
    }

    const [items, totalItems] = await builder.getManyAndCount();
    return {
      items,
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getAllMakers(page = 1, limit = 20, filters = null, sorts = null) {
    let builder = this.makerRepository
      .createQueryBuilder('maker')
      .take(limit)
      .skip((page - 1) * limit);

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

    builder = applyFiltersToBuilder(builder, filters);

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

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

  /**
   * -------------------------------------------------------
   */
  async getAllMakerBrands(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    productIdForMapping = null,
    wage = null,
  ) {
    let builder = this.makerBrandRepository
      .createQueryBuilder('makerBrand')
      .leftJoinAndSelect('makerBrand.maker', 'maker')
      .take(limit)
      .skip((page - 1) * limit);

    if (productIdForMapping) {
      builder.leftJoin(
        'makerBrand.productMappings',
        'productMappings',
        'productMappings.productId = :productId',
        { productId: productIdForMapping },
      );
      builder.addSelect('productMappings.wagePrice');
      if (wage) builder.andWhere('productMappings.wagePrice = :wage', { wage });
    }

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

    builder = applyFiltersToBuilder(builder, filters);

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

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

  /**
   * -------------------------------------------------------
   */
  async getMakerBrandById(id: number, hasRelation = true) {
    const builder = this.makerBrandRepository
      .createQueryBuilder('makerBrand')
      .andWhere({ id });

    if (hasRelation) {
      builder.leftJoinAndSelect('makerBrand.maker', 'maker');
    }

    return await builder.getOne();
  }

  /**
   * -------------------------------------------------------
   * Admin
   * update MakerBrand
   */
  async updateMakerBrand(
    makerBrandId: number,
    dto: UpdateMakerBrandDto,
    image: Express.Multer.File,
    operatorUserId: string,
  ) {
    const makerBrand = await this.getMakerBrandById(makerBrandId, false);

    if (image) {
      dto.image = `/uploads/brands/${image.filename}`;
    }
    const data = await this.makerBrandRepository
      .createQueryBuilder()
      .update()
      .set({ ...dto, updatedAt: new Date() })
      .where({ id: makerBrandId })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.maker_brand,
      action: LogAction.update,
      operatorUserId,
      message: `مشخصات برند خودروی ${makerBrand.name} ویرایش شد.`,
      affectedId: String(makerBrandId),
      item: makerBrand,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async getMakerById(id: number) {
    return await this.makerRepository
      .createQueryBuilder('makers')
      .andWhere({ id })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async addCar(dto: CreateCarDto, operatorUserId: string) {
    const { identifiers } = await this.carRepository
      .createQueryBuilder()
      .insert()
      .values({ ...dto, createdAt: new Date(), updatedAt: new Date() })
      .execute();

    const newCarId = identifiers[0].id;
    const newCar = await this.carRepository.findOne({
      where: { id: newCarId },
      relations: ['makerBrand', 'user'],
    });

    // Adding a log
    await this.logService.add({
      type: LogType.car,
      action: LogAction.insert,
      operatorUserId,
      message: `خودروی جدید ${
        newCar.makerBrand.name
      } به شماره انتظامی ${newCar.number.split('***').join('-')} برای کاربر ${
        newCar.user.name
      } ${newCar.user.surName} موردنظر اضافه شد`,
      affectedId: newCarId,
      item: {
        id: newCar.id,
        number: newCar.number,
        type: newCar.type,
        makerBrandId: newCar.makerBrandId,
        userId: newCar.userId,
      },
    });

    return newCar;
  }

  /**
   * -------------------------------------------------------
   */
  async updateCar(
    id: number,
    dto: UpdateCarDto,
    operatorUserId: string = null,
  ) {
    const car = await this.carRepository.findOne({
      where: { id },
      relations: ['makerBrand', 'user'],
    });

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

    if (operatorUserId) {
      // Adding a log
      await this.logService.add({
        type: LogType.car,
        action: LogAction.update,
        operatorUserId,
        message: `مشخصات خودروی ${
          car.makerBrand.name
        } به شماره انتظامی ${car.number.split('***').join('-')} کاربر ${
          car.user.name
        } ${car.user.surName} ویرایش شد.`,
        affectedId: String(id),
        item: {
          id: car.id,
          number: car.number,
          type: car.type,
          makerBrandId: car.makerBrandId,
          userId: car.userId,
        },
      });
    }

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async addMakerBrand(
    dto: CreateMakerBrandDto,
    image: Express.Multer.File,
    operatorUserId: string,
  ) {
    const newBrand = new MakerBrandEntity();
    for (const key in dto) {
      newBrand[key] = dto[key];
    }

    newBrand.image = `/uploads/brands/${image.filename}`;
    newBrand.displayOrder = await getLastDisplayOrder(MakerBrandEntity.name);

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

    const { identifiers } = await this.makerBrandRepository
      .createQueryBuilder()
      .insert()
      .values(newBrand)
      .execute();

    const newBrandId = identifiers[0].id;
    const data = await this.makerBrandRepository.findOne(newBrandId);

    // Adding a log
    await this.logService.add({
      type: LogType.maker_brand,
      action: LogAction.insert,
      operatorUserId,
      message: `برند خودروی ${newBrand.name} اضافه شد.`,
      affectedId: newBrandId,
      item: newBrand,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   * DELETE /makerBrand/1/admin
   */
  async deleteMakerBrand(id: number, operatorUserId: string) {
    const makerBrand = await this.makerBrandRepository.findOne(id);

    const data = await this.makerBrandRepository
      .createQueryBuilder()
      .update()
      .set({ deleted: true, updatedAt: new Date() })
      .where({ id })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.maker_brand,
      action: LogAction.delete,
      operatorUserId,
      message: `برند خودروی ${makerBrand.name} حذف شد.`,
      affectedId: String(id),
      item: makerBrand,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   * DELETE /maker/1/admin
   */
  async deleteMaker(id: number, operatorUserId: string) {
    const maker = await this.makerRepository.findOne(id);

    const data = await this.makerRepository
      .createQueryBuilder()
      .update()
      .set({ deleted: true, updatedAt: new Date() })
      .where({ id })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.maker,
      action: LogAction.delete,
      operatorUserId,
      message: `خودروساز ${maker.name} حذف شد.`,
      affectedId: String(id),
      item: maker,
    });

    return data;
  }
  /**
   * -------------------------------------------------------
   */
  async deleteCar(id: number, operatorUserId: string) {
    const car = await this.carRepository.findOne({
      where: { id },
      relations: ['makerBrand', 'user'],
    });

    const data = await this.carRepository
      .createQueryBuilder()
      .update()
      .set({ deleted: true, updatedAt: new Date() })
      .where({ id })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.car,
      action: LogAction.delete,
      operatorUserId,
      message: `خودروی ${car.makerBrand.name} به شماره انتظامی ${car.number
        .split('***')
        .join('-')} کاربر ${car.user.name} ${car.user.surName} حذف شد.`,
      affectedId: String(id),
      item: {
        id: car.id,
        number: car.number,
        type: car.type,
        makerBrandId: car.makerBrandId,
        userId: car.userId,
      },
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async addMaker(
    dto: CreateMakerDto,
    image: Express.Multer.File,
    operatorUserId: string,
  ) {
    const newMaker = new MakerEntity();
    for (const key in dto) {
      newMaker[key] = dto[key];
    }

    newMaker.image = `/uploads/companies/${image.filename}`;
    newMaker.displayOrder = await getLastDisplayOrder(MakerEntity.name);

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

    const { identifiers } = await this.makerRepository
      .createQueryBuilder()
      .insert()
      .values(newMaker)
      .execute();

    const newMakerId = identifiers[0].id;
    const data = await this.makerRepository.findOne(newMakerId);

    // Adding a log
    await this.logService.add({
      type: LogType.maker,
      action: LogAction.insert,
      operatorUserId,
      message: `خودروساز ${newMaker.name} اضافه شد.`,
      affectedId: newMakerId,
      item: newMaker,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   * Admin
   * update Maker
   */
  async updateMaker(
    id: number,
    dto: UpdateMakerDto,
    image: Express.Multer.File,
    operatorUserId: string,
  ) {
    const maker = await this.getMakerById(id);

    if (image) {
      dto.image = `/uploads/companies/${image.filename}`;
    }
    const data = await this.makerRepository
      .createQueryBuilder()
      .update()
      .set({ ...dto, updatedAt: new Date() })
      .where({ id })
      .execute();

    // Adding a log
    await this.logService.add({
      type: LogType.maker,
      action: LogAction.update,
      operatorUserId,
      message: `خودروساز ${dto.name} آپدیت شد.`,
      affectedId: String(id),
      item: maker,
    });

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async getMakerBrandFull() {
    return await this.makerBrandRepository.find({
      select: ['id', 'name'],
    });
  }

  /**
   * -------------------------------------------------------
   */
  async makerBrandProductMapping(
    makerBrandId: number,
    dto: MakerBrandProductDto,
    operatorUserId: string,
  ) {
    const makerBrand = await this.makerBrandRepository.findOne(makerBrandId);

    const data = await this.productService.makerBrandToProductMapping(
      makerBrandId,
      dto,
    );

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

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  async foundCarByUserId(id: number, userId: string) {
    return await this.carRepository.findOne({ id, userId });
  }

  /**
   * -------------------------------------------------------
   */
  async foundCarByUserIdAndNumber(number: string, userId: string) {
    return await this.carRepository.findOne({ number, userId });
  }

  /**
   * -------------------------------------------------------
   */
  async getOilGovernmentAllocatedByLastChecked(carId: number) {
    const found = await this.carRepository.findOne({
      id: carId,
      oilGovSuccessChecked: MoreThanOrEqual(
        moment().subtract(24, 'hours').format('YYYY-MM-DD'),
      ),
    });

    if (found) {
      return found.oilGovAllocation || 8;
    }
    return null;
  }

  /**
   * -------------------------------------------------------
   */
  async handleAllocateOilGovernment() {
    const checkTime = moment().isBetween(
      moment('09:00:00', 'hh:mm:ss'),
      moment('23:00:00 ', 'hh:mm:ss'),
    );

    if (!checkTime) {
      return false;
    }

    const list = await this.carRepository
      .createQueryBuilder('car')
      .innerJoinAndSelect('car.user', 'user')
      .where(
        `(car.oilGovSuccessChecked IS NULL OR (car.oilGovSuccessChecked IS NOT NULL AND car.oilGovSuccessChecked < :successOneMonthAgo))`,
        {
          successOneMonthAgo: moment()
            .subtract(1, 'months')
            .format('YYYY-MM-DD'),
        },
      )
      .addOrderBy('car.createdAt', 'DESC')
      .take(100)
      .getMany();

    // const sharedQuery = `car.vinCode != "" AND
    //   car.engineCode != "" AND
    //   (
    //     (car.oilGovSuccessChecked IS NULL AND oilGovForce = 1) OR
    //     (car.oilGovSuccessChecked IS NULL AND oilGovLastChecked < :lastOneWeekAgo AND oilGovForce = 0) OR
    //     (car.oilGovSuccessChecked IS NOT NULL AND car.oilGovSuccessChecked < :successThreeMonthAgo)
    //   )`;
    // const sharedParameters = {
    //   successThreeMonthAgo: moment().subtract(3, 'months').format('YYYY-MM-DD'),
    //   lastOneWeekAgo: moment().subtract(1, 'weeks').format('YYYY-MM-DD'),
    // };

    // const list = await this.carRepository
    //   .createQueryBuilder('car')
    //   .innerJoinAndSelect('car.user', 'user', 'user.nationalCode != ""')
    //   .leftJoin('car.orders', 'orders')
    //   .where(`(${sharedQuery} AND orders.id IS NULL)`, sharedParameters)
    //   .orWhere(
    //     `(${sharedQuery} AND
    //       orders.id IS NOT NULL AND
    //         (
    //           orders.governmentOilTrackingCode IS NULL OR
    //           orders.governmentOilTrackingCode = "" OR
    //           (orders.governmentOilTrackingCode != "" AND orders.createdAt < :orderThreeMonthAgo)
    //         )
    //       )`,
    //     {
    //       orderThreeMonthAgo: moment()
    //         .subtract(3, 'months')
    //         .format('YYYY-MM-DD'),
    //       ...sharedParameters,
    //     },
    //   )
    //   .orderBy('car.oilGovPriority', 'ASC')
    //   .addOrderBy('car.createdAt', 'ASC')
    //   .take(50)
    //   .getMany();

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

      await this._checkOilGovernmentAndSendSMS(car, 0);
      await sleep(500);
    }
  }

  /**
   * -------------------------------------------------------
   */
  private async _checkOilGovernmentAndSendSMS(car, trying = 0) {
    try {
      // const cleanMobile =
      //   car.user.mobile.substring(0, 1) === '0'
      //     ? car.user.mobile.substring(1)
      //     : car.user.mobile;

      const allocation = 8;
      // const { allocation }: any = await this.samtService.GetOilAllocation(
      //   { mobile: cleanMobile, nationalCode: car.user.nationalCode },
      //   { vinCode: car.vinCode, engineCode: car.engineCode },
      // );

      // if (allocation === 0 && car.oilGovForce) {
      //   // sms
      //   await this.sms.notifyCustomersForZeroAllocateForceOilGovernment(
      //     car.user.mobile,
      //     car.user.surName,
      //     car.number,
      //   );

      //   //update
      //   await this._updateOilGov(car.id, {
      //     oilGovSuccessChecked: new Date(),
      //     oilGovPriority: 5,
      //     oilGovForce: false,
      //     oilGovAllocation: allocation,
      //     oilGovErrorCode: null,
      //     oilGovMessage: `ظرفیت روغن دولتی خودروی واجد شرایط درخواستی، صفر می باشد.`,
      //   });
      // }else if (allocation !== 0) {
      // sms
      await this.sms.notifyCustomersForAllocateOilGovernment(
        car.user.mobile,
        car.user.surName,
        allocation,
        car.number,
      );

      if (car.oilGovForce) {
        //update
        await this._updateOilGov(car.id, {
          oilGovSuccessChecked: new Date(),
          oilGovPriority: 5,
          oilGovForce: false,
          oilGovAllocation: allocation,
          oilGovErrorCode: null,
          oilGovMessage: `استعلام ظرفیت روغن دولتی خودروی واجد شرایط درخواستی ${allocation} لیتر می باشد.`,
        });
      } else {
        await this._updateOilGov(car.id, {
          oilGovSuccessChecked: new Date(),
          oilGovPriority: car.oilGovPriority + 1,
          oilGovAllocation: allocation,
          oilGovErrorCode: null,
          oilGovMessage: `استعلام ظرفیت روغن دولتی خودروی واجد شرایط موجود، ${allocation} لیتر می باشد.`,
        });
      }
      // }

      return allocation;
    } catch (err) {
      if (trying === 5 || err?.code !== 10001) {
        if (car.oilGovForce) {
          if (car.oilGovPriority === 1) {
            // sms
            await this.sms.notifyCustomersForErrorAllocateForceOilGovernment(
              car.user.mobile,
              car.user.surName || '-',
              car.number,
            );

            //update
            await this._updateOilGov(car.id, {
              oilGovPriority: 5,
              oilGovAllocation: null,
              oilGovForce: false,
              oilGovErrorCode: err?.code || null,
              oilGovMessage: err?.message || null,
            });
          } else {
            // update
            await this._updateOilGov(car.id, {
              oilGovPriority: car.oilGovPriority - 1,
              oilGovAllocation: null,
              oilGovErrorCode: err?.code || null,
              oilGovMessage: err?.message || null,
            });
          }
        } else {
          // update
          await this._updateOilGov(car.id, {
            oilGovPriority: car.oilGovPriority + 1,
            oilGovAllocation: null,
            oilGovErrorCode: err?.code || null,
            oilGovMessage: err?.message || null,
          });
        }

        return err;
      }

      // خطا در سرویس بیمه
      if (err?.code === 10001) {
        await sleep(3000);
        return await this._checkOilGovernmentAndSendSMS(car, trying + 1);
      }
    }
  }

  private async _updateOilGov(id, data) {
    await this.carRepository
      .createQueryBuilder('car')
      .update()
      .set({ ...data, oilGovLastChecked: new Date() })
      .where({ id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  async getValidCarsWithNationalCode(limit = 5) {
    return await this.carRepository
      .createQueryBuilder('car')
      .innerJoinAndSelect('car.user', 'user', 'user.nationalCode != ""')
      .where('car.makeYear = "-1"')
      .take(limit)
      .getMany();
  }

  /**
   * -------------------------------------------------------
   */
  async findMakerBrandByName(name: string) {
    return await this.makerBrandRepository.findOne({ name });
  }
}
