import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { ErrorService } from '../../error/error.service';
import {
  DrivingOffenseEntity,
  DrivingOffenseStatus,
  DrivingOffenseType,
} from './entities/driving-offense.entity';
import {
  DrivingOffenseBillEntity,
  DrivingOffenseBillStatus,
} from './entities/driving-offense-bill.entity';
import { CarService } from '../car/car.service';
import { SiteInfoService } from '../siteInfo/site-info.service';
import { VeneshService } from '../../thirdParty/venesh/venesh.service';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
} from 'src/utils';
import { CreateDrivingOffenseRequestDto } from './dto/create-driving-offense-request.dto';
import { PayDrivingOffenseInquiryDto } from './dto/pay-driving-offense-inquiry.dto';
import { PaymentService } from '../payment/payment.service';
import { UserEntity } from '../user/entities/user.entity';
import { PaymentType } from '../payment/entities/payment.entity';
import { UserService } from '../user/user.service';
import { PayDrivingOffenseBillDto } from './dto/pay-driving-offense-bill.dto';
import { GhabzinoService } from '../../thirdParty/ghabzino/ghabzino.service';

@Injectable()
export class DrivingOffenseService {
  constructor(
    @InjectRepository(DrivingOffenseEntity)
    private drivingOffenseRepository: Repository<DrivingOffenseEntity>,
    @InjectRepository(DrivingOffenseBillEntity)
    private drivingOffenseBillRepository: Repository<DrivingOffenseBillEntity>,

    private error: ErrorService,
    private veneshService: VeneshService,
    private ghabzinoService: GhabzinoService,
    private userService: UserService,
    private siteInfoService: SiteInfoService,
    private carService: CarService,

    @Inject(forwardRef(() => PaymentService))
    private readonly paymentService: PaymentService,
  ) {}

  /**
   * -------------------------------------------------------
   * POST /driving-offenses/request/me
   */
  async addRequest(dto: CreateDrivingOffenseRequestDto, user: UserEntity) {
    const setting = await this.siteInfoService.getInfo([
      'drivingOffenseInquiryBasicAmount',
      'drivingOffenseInquiryAdvancedAmount',
    ]);

    let inquiryAmount = 0;
    if (dto.type === DrivingOffenseType.basic) {
      inquiryAmount = setting.drivingOffenseInquiryBasicAmount;
    } else {
      if (!user.nationalCode) {
        this.error.unprocessableEntity(['ورود کد ملی اجباری است!']);
      }

      inquiryAmount = setting.drivingOffenseInquiryAdvancedAmount;
    }

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

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

    const { identifiers } = await this.drivingOffenseRepository
      .createQueryBuilder()
      .insert()
      .values({
        ...dto,
        userId: user.id,
        inquiryAmount,
        status: inquiryAmount
          ? DrivingOffenseStatus.pending
          : DrivingOffenseStatus.paid_inquiry,
        plateNumber: foundCar.number,
        createdAt: new Date(),
        updatedAt: new Date(),
      })
      .execute();

    return { id: identifiers[0].id };
  }

  /**
   * -------------------------------------------------------
   * POST /driving-offenses/pay-inquiry/me
   */
  async payInquiry(dto: PayDrivingOffenseInquiryDto, user: UserEntity) {
    const drivingOffense = await this.drivingOffenseRepository.findOne({
      where: {
        id: dto.drivingOffenseId,
        userId: user.id,
        status: DrivingOffenseStatus.pending,
      },
      relations: ['user'],
    });

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

    let bankPayAmount = drivingOffense.inquiryAmount;
    let usedWalletAmount = null;

    // Using the wallet
    if (dto.useWallet) {
      const totalWallet = user.realWallet + user.virtualWallet;

      // Enough money in wallet
      if (totalWallet >= drivingOffense.inquiryAmount) {
        drivingOffense.status = DrivingOffenseStatus.paid_inquiry;
        await drivingOffense.save();

        await this.userService.reduceBothWallets(
          user.id,
          drivingOffense.inquiryAmount,
        );

        return { bankUrl: null, paid: true };
      }

      // After payment, totalWallet will be reduced from the wallet
      bankPayAmount -= totalWallet;
      usedWalletAmount = totalWallet;
    }

    // Going to the bank
    const { paymentId, bankUrl } = await this.paymentService.createPayLink(
      bankPayAmount,
      usedWalletAmount,
      PaymentType.offense_inquiry,
      dto.payGateway,
      drivingOffense.id,
      user,
    );

    drivingOffense.paymentInquiryId = paymentId;
    await drivingOffense.save();

    return { bankUrl, paid: false };
  }

  /**
   * -------------------------------------------------------
   * GET /driving-offenses/1/do-inquiry/me
   * call from front or payment service
   */
  async doInquiry(
    paymentId: number = null,
    drivingOffenseId: number = null,
    user: UserEntity = null,
  ) {
    let drivingOffense: DrivingOffenseEntity;

    // Request from front-end
    if (drivingOffenseId) {
      drivingOffense = await this.drivingOffenseRepository.findOne({
        where: { id: drivingOffenseId, userId: user.id },
        relations: ['user', 'drivingOffenseBills'],
      });

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

      if (
        drivingOffense?.inquiryAmount &&
        drivingOffense?.status === DrivingOffenseStatus.pending
      ) {
        this.error.unprocessableEntity(['درخواست شما نامعتبر است']);
      }
    }
    // Request after payment by paymentService
    else {
      drivingOffense = await this.drivingOffenseRepository.findOne({
        where: { paymentInquiryId: paymentId },
        relations: ['user', 'drivingOffenseBills'],
      });
    }

    // Already inquired the offense
    if (
      [
        DrivingOffenseStatus.inquired,
        DrivingOffenseStatus.paid_offense,
      ].indexOf(drivingOffense.status) !== -1
    ) {
      delete drivingOffense.user;
      return drivingOffense;
    }

    drivingOffense.status = DrivingOffenseStatus.paid_inquiry;
    await drivingOffense.save();

    // Inquiring the offense
    if (drivingOffense.type === DrivingOffenseType.basic) {
      //   const result = await this.veneshService.basicDrivingOffenseInquiry(
      //     drivingOffense.user.nationalCode,
      //     drivingOffense.user.mobile,
      //     drivingOffense.plateNumber,
      //   );

      const result = await this.ghabzinoService.basicDrivingOffenseInquiry(
        drivingOffense.user.nationalCode,
        drivingOffense.user.mobile,
        drivingOffense.plateNumber,
      );

      if (!result) {
        // from fron-end
        if (drivingOffenseId) {
          // Updating the user wallet
          await this.userService.updateWallet(
            drivingOffense.userId,
            drivingOffense.inquiryAmount,
          );
        }
        this.error.internalServerError(['خطایی رخ داده است، مجددا تلاش کنید!']);
      }

      // update offense with result (ghabzino)
      drivingOffense.traceNumber = result.TraceNumber;
      drivingOffense.offenseAmount = +result.TotalAmount / 10;
      drivingOffense.totalPayId = result.TotalPayId;
      drivingOffense.totalBillId = result.TotalBillId;
      drivingOffense.complaintCode = result.ComplaintCode;
      drivingOffense.complaintStatus = result.ComplaintStatus;
      drivingOffense.status = DrivingOffenseStatus.inquired;
      drivingOffense.updatedAt = new Date();
      await drivingOffense.save();
    } else {
      // const result = await this.veneshService.advancedDrivingOffenseInquiry(
      //   drivingOffense.user.nationalCode,
      //   drivingOffense.user.mobile,
      //   drivingOffense.plateNumber,
      // );
      const result = await this.ghabzinoService.advancedDrivingOffenseInquiry(
        drivingOffense.user.nationalCode,
        drivingOffense.user.mobile,
        drivingOffense.plateNumber,
      );

      if (!result) {
        // from fron-end
        if (drivingOffenseId) {
          // Updating the user wallet
          await this.userService.updateWallet(
            drivingOffense.userId,
            drivingOffense.inquiryAmount,
          );
        }
        this.error.internalServerError(['خطایی رخ داده است، مجددا تلاش کنید!']);
      }

      // update offense with result (ghabzino)
      drivingOffense.traceNumber = result.TraceNumber;
      drivingOffense.offenseAmount = +result.TotalAmount / 10;
      drivingOffense.totalPayId = result.TotalPayId;
      drivingOffense.totalBillId = result.TotalBillId;
      drivingOffense.status = DrivingOffenseStatus.inquired;
      drivingOffense.updatedAt = new Date();
      await drivingOffense.save();

      // insert all bills
      await this.drivingOffenseBillRepository
        .createQueryBuilder()
        .insert()
        .values(
          result.Bills.map((bill) => ({
            drivingOffenseId: drivingOffense.id,
            amount: +bill.Amount / 10,
            billId: bill.BillId,
            payId: bill.PayId,
            city: bill.City,
            date: bill.Date,
            deliveryType: bill.DeliveryType,
            location: bill.Location,
            type: bill.Type,
            typeId: bill.TypeId,
            imageId: bill.ImageId,
            status: DrivingOffenseBillStatus.unpaid,
            serialNumber: bill.SerialNumber,
            officerIdentificationCode: bill.OfficerIdentificationCode,
            createdAt: new Date(),
            updatedAt: new Date(),
          })),
        )
        .execute();
    }

    return await this.drivingOffenseRepository.findOne({
      where: { id: drivingOffense.id },
      relations: ['drivingOffenseBills'],
    });
  }

  /**
   * -------------------------------------------------------
   * POST /driving-offenses/1/pay-bills/me
   */
  // async payBills(dto: PayDrivingOffenseBillDto, user: UserEntity) {
  //   const dbBills = await this.drivingOffenseBillRepository
  //     .createQueryBuilder('bill')
  //     .innerJoinAndSelect(
  //       'bill.drivingOffense',
  //       'drivingOffense',
  //       'drivingOffense.userId = :userId',
  //       { userId: user.id },
  //     )
  //     .andWhere('id IN (:...ids)', { ids: dto.bills })
  // .andWhere({ status: DrivingOffenseBillStatus.unpaid })
  //     .getMany();

  //   if (dbBills.length === 0) {
  //     this.error.unprocessableEntity(['درخواست شما نامعتبر است']);
  //   }

  //   // sum the amount of the bills
  //   let bankPayAmount = dbBills.reduce((acc, dbBill) => dbBill.amount + acc, 0);
  //   let usedWalletAmount = null;

  //   // clculating wallet
  //   // Using the wallet
  //   if (dto.useWallet) {
  //     const totalWallet = user.realWallet + user.virtualWallet;

  //     // Enough money in wallet
  //     if (totalWallet >= bankPayAmount) {
  //       await this.drivingOffenseBillRepository
  //         .createQueryBuilder()
  //         .update()
  //         .set({ status: DrivingOffenseBillStatus.paid })
  //         .where('id IN (:...ids)', { ids: dbBills.map((dbBill) => dbBill.id) })
  //         .execute();

  //       await this.userService.reduceBothWallets(user.id, bankPayAmount);

  //       return { bankUrl: null, paid: true };
  //     }

  //     // After payment, totalWallet will be reduced from the wallet
  //     bankPayAmount -= totalWallet;
  //     usedWalletAmount = totalWallet;
  //   }

  //   // else Going to the bank
  //   const { paymentId, bankUrl } = await this.paymentService.createPayLink(
  //     bankPayAmount,
  //     usedWalletAmount,
  //     PaymentType.offense_pay,
  //     dto.payGateway,
  //     dbBills[0].drivingOffenseId,
  //     user,
  //   );

  //   // update dbBiils with paymentId
  //   await this.drivingOffenseBillRepository
  //     .createQueryBuilder()
  //     .update()
  //     .set({ paymentId })
  //     .where('id IN (:...ids)', { ids: dbBills.map((dbBill) => dbBill.id) })
  //     .execute();

  //   return { bankUrl, paid: false };
  // }

  /**
   * -------------------------------------------------------
   * POST /driving-offenses/1/do-paying/me
   * call from front or payment service
   */
  // async doPaying(
  //   paymentId: number = null,
  //   drivingOffenseId: number = null,
  //   user: UserEntity = null,
  // ) {
  //   let dbBills;

  //   if (paymentId) {
  //     dbBills = await this.drivingOffenseBillRepository
  //       .createQueryBuilder('bill')
  //       .innerJoinAndSelect('bill.drivingOffense', 'drivingOffense')
  //       .andWhere({ paymentId })
  //       .andWhere({ status: DrivingOffenseBillStatus.unpaid })
  //       .getMany();

  //     // update status to paid
  //     await this.drivingOffenseBillRepository
  //       .createQueryBuilder()
  //       .update()
  //       .set({ status: DrivingOffenseBillStatus.paid })
  //       .where('id IN (:...ids)', { ids: dbBills.map((dbBill) => dbBill.id) })
  //       .execute();
  //   } else {
  //     dbBills = await this.drivingOffenseBillRepository
  //       .createQueryBuilder('bill')
  //       .innerJoinAndSelect(
  //         'bill.drivingOffense',
  //         'drivingOffense',
  //         'drivingOffense.userId = :userId',
  //         { userId: user.id },
  //       )
  //       .andWhere({ status: DrivingOffenseBillStatus.paid })
  //       .andWhere({ drivingOffenseId })
  //       .getMany();
  //   }

  //   // loop call venesh by payId and billId
  //   for (let i = 0; i < dbBills.length; i++) {
  //     const dbBill = dbBills[i];

  //     try {
  //       await this.veneshService.payDrivingOffenseBill(
  //         dbBill.billId,
  //         dbBill.payId,
  //       );

  //       // update status to done
  //       await this.drivingOffenseBillRepository
  //         .createQueryBuilder()
  //         .update()
  //         .set({ status: DrivingOffenseBillStatus.done })
  //         .where({ id: dbBill.id })
  //         .execute();
  //     } catch (err) {
  //       console.log(err);
  //       this.error.internalServerError([
  //         'خطایی در پرداخت قبوض خلافی، رخ داده است.',
  //       ]);
  //     }
  //   }

  //   return { id: dbBills[0].drivingOffenseId };
  // }

  /**
   * -------------------------------------------------------
   * GET /driving-offenses/admin or me
   */
  async getAll(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    userId = null,
  ) {
    let builder = this.drivingOffenseRepository
      .createQueryBuilder('drivingOffense')
      .leftJoinAndSelect('drivingOffense.car', 'car')
      .innerJoin('drivingOffense.user', 'user')
      .addSelect([
        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
        'user.licenseNumber',
      ])
      .take(limit)
      .skip((page - 1) * limit);

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

    builder = applyFiltersToBuilder(builder, filters);

    if (userId) {
      builder.andWhere({ userId });
    }
    const [items, totalItems] = await builder.getManyAndCount();

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

  /**
   * -------------------------------------------------------
   * GET /driving-offenses/1/admin or me
   */
  async getById(id: number, userId = null) {
    const builder = this.drivingOffenseRepository
      .createQueryBuilder('drivingOffense')
      .leftJoinAndSelect(
        'drivingOffense.drivingOffenseBills',
        'drivingOffenseBills',
      )
      .leftJoinAndSelect('drivingOffense.car', 'car')
      .addSelect(['car.id', 'car.makerBrandId', 'car.modelId', 'car.number'])
      .leftJoinAndSelect('drivingOffense.paymentInquiry', 'paymentInquiry')
      .innerJoin('drivingOffense.user', 'user')
      .addSelect([
        'user.id',
        'user.name',
        'user.surName',
        'user.mobile',
        'user.licenseNumber',
      ])
      .andWhere({ id });
    if (userId) {
      builder.andWhere({ userId });
    }

    return await builder.getOne();
  }

  /**
   * -------------------------------------------------------
   * GET /driving-offenses/1/images/me (ghabzino)
   */
  async getDrivingOffenseImages(drivingOffenseId: number, user: UserEntity) {
    const bills = await this.drivingOffenseBillRepository
      .createQueryBuilder('bill')
      .innerJoin(
        'bill.drivingOffense',
        'drivingOffense',
        'drivingOffense.userId = :userId',
        { userId: user.id },
      )
      .where({ drivingOffenseId })
      .andWhere('bill.imageId IS NOT NULL')
      .getMany();

    const result = [];
    for (let i = 0; i < bills.length; i++) {
      try {
        const imageData = await this.ghabzinoService.getDrivingOffenseImage(
          user.mobile,
          bills[i].imageId,
        );

        result.push({ id: bills[i].id, imagePath: imageData.VehicleImageUrl });
      } catch (e) {
        console.log(e);
      }
    }

    return result;
  }

  /**
   * -------------------------------------------------------
   * GET /driving-offenses/image/1/me (ghabzino)
   */
  async getDrivingOffenseImage(drivingOffenseBillId: number, user: UserEntity) {
    const bill = await this.drivingOffenseBillRepository
      .createQueryBuilder('bill')
      .innerJoin(
        'bill.drivingOffense',
        'drivingOffense',
        'drivingOffense.userId = :userId',
        { userId: user.id },
      )
      .where({ id: drivingOffenseBillId })
      .andWhere('bill.imageId IS NOT NULL')
      .getOne();

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

    try {
      const imageData = await this.ghabzinoService.getDrivingOffenseImage(
        user.mobile,
        bill.imageId,
      );

      return imageData.VehicleImageUrl;
    } catch (e) {
      console.log(e);
      this.error.unprocessableEntity(['خطایی رخ داده است، دوباره تلاش کنید']);
    }
  }

  /**
   * -------------------------------
   * pay driving offense bill (ghabzino)
   */
  async payDrivingOffenseBill(dto: PayDrivingOffenseBillDto, user: UserEntity) {
    // based on offenceId
    if (dto.offenceId) {
      const dbOffence = await this.drivingOffenseRepository.findOne({
        id: dto.offenceId,
        userId: user.id,
        status: DrivingOffenseStatus.inquired,
      });

      const bills = [
        {
          PaymentID: dbOffence.totalPayId,
          BillID: dbOffence.totalBillId,
        },
      ];

      const { bankUrl, paymentKey } =
        await this.ghabzinoService.payDrivingOffenseBill(bills, user.mobile);

      dbOffence.paymentKey = paymentKey;
      await dbOffence.save();

      return { bankUrl };
    }

    // based on bill IDs
    const dbBills = await this.drivingOffenseBillRepository
      .createQueryBuilder('bill')
      .innerJoin(
        'bill.drivingOffense',
        'drivingOffense',
        'drivingOffense.userId = :userId',
        { userId: user.id },
      )
      .andWhere('bill.id IN (:...billIds)', {
        billIds: dto.billIds,
      })
      .andWhere('bill.status = :status', {
        status: DrivingOffenseBillStatus.unpaid,
      })
      .getMany();

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

    const bills = dbBills.map((bill) => ({
      PaymentID: bill.payId,
      BillID: bill.billId,
    }));

    const { bankUrl, paymentKey } =
      await this.ghabzinoService.payDrivingOffenseBill(bills, user.mobile);

    // update dbBiils with paymentKey
    await this.drivingOffenseBillRepository
      .createQueryBuilder()
      .update()
      .set({ paymentKey })
      .where('id IN (:...ids)', { ids: dbBills.map((dbBill) => dbBill.id) })
      .execute();

    return { bankUrl };
  }

  /**
   * -------------------------------------------------------
   * POST /driving-offenses/1/pay-result-bills/me
   */
  async resultDrivingOffenseBill(paymentKey: string) {
    const result = await this.ghabzinoService.resultDrivingOffenseBill(
      paymentKey,
    );

    const output = [];

    for (let i = 0; i < result.Bills.length; i++) {
      const bill = result.Bills[i];

      const dbBill = await this.drivingOffenseBillRepository.findOne({
        payId: bill.PaymentID,
        billId: bill.BillID,
        paymentKey,
      });

      if (dbBill) {
        if (bill.Paid) {
          await this.drivingOffenseBillRepository
            .createQueryBuilder()
            .update()
            .set({ status: DrivingOffenseBillStatus.done })
            .where({
              payId: bill.PaymentID,
              billId: bill.BillID,
            })
            .execute();
        }
        output.push({ section: 'bill', ...dbBill });
      }
      // -------------------
      else {
        const dbOffense = await this.drivingOffenseRepository.findOne({
          paymentKey,
        });

        if (dbOffense) {
          dbOffense.status = DrivingOffenseStatus.paid_offense;
          await dbOffense.save();

          output.push({ section: 'offence', ...dbOffense });
        }
      }
    }

    return output;
  }
}
