import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ErrorService } from '../../error/error.service';
import {
  PaymentEntity,
  PaymentGateway,
  PaymentGatewayCode,
  PaymentStatus,
  PaymentType,
} from './entities/payment.entity';
import { Repository } from 'typeorm';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
  retryOperation,
} from 'src/utils';
import { ZarinpalService } from '../../gateway/zarinpal/zarinpal.service';
import { BankMeliService } from '../../gateway/bankMeli/bankMeli.service';
import { UserEntity } from '../user/entities/user.entity';
import { FreewayTollService } from '../freeway-toll/freeway-toll.service';
import { DrivingOffenseService } from '../drivinig-offense/driving-offense.service';
import { NegativeScoreService } from '../negative-score/negative-score.service';
import { UserService } from '../user/user.service';
import { OrderService } from '../order/order.service';
import { SnappPayService } from '../../gateway/snappPay/snappPay.service';

@Injectable()
export class PaymentService {
  private apiPaymentUrl = `https://api.auto-tik.com/payments`;
  private sitePaymentResult = `https://auto-tik.com/pay-redirect`;

  constructor(
    @InjectRepository(PaymentEntity)
    private paymentRepository: Repository<PaymentEntity>,

    private userService: UserService,
    private zarinpalService: ZarinpalService,
    private bankMeliService: BankMeliService,
    private snappPayService: SnappPayService,
    private error: ErrorService,

    @Inject(forwardRef(() => DrivingOffenseService))
    private readonly drivingOffenseService: DrivingOffenseService,
    @Inject(forwardRef(() => NegativeScoreService))
    private readonly negativeScoreService: NegativeScoreService,
    @Inject(forwardRef(() => FreewayTollService))
    private readonly freewayTollService: FreewayTollService,
    @Inject(forwardRef(() => OrderService))
    private readonly orderService: OrderService,
  ) {}

  /**
   * -------------------------------------------------------
   */
  async getRecordById(paymentId: number) {
    return await this.paymentRepository.findOne(paymentId);
  }

  /**
   * -------------------------------------------------------
   * GET /payments/admin
   */
  async getAll(page = 1, limit = 20, filters = null, sorts = null) {
    let builder = this.paymentRepository
      .createQueryBuilder('payment')
      .innerJoinAndSelect('payment.order', 'order')
      .innerJoinAndSelect('payment.user', 'user')
      .take(limit)
      .skip((page - 1) * limit);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('payment.createdAt', 'ASC'); //////////// desc or asc ?
    }

    builder = applyFiltersToBuilder(builder, filters);

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

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

  /**
   * -------------------------------------------------------
   * amount based on Toman
   */
  async createPayLink(
    amount: number,
    usedWalletAmount: number,
    type: PaymentType,
    gateway: PaymentGateway,
    orderId: number,
    user: UserEntity,
  ) {
    try {
      let bankResponse;
      let businessPartnerCode = null;

      // ZARINPAL
      if (gateway === PaymentGateway.zarinpal) {
        bankResponse = await this.zarinpalService.paymentRequest(
          amount,
          `${this.apiPaymentUrl}/zarinpal/verify`,
        );
      }
      // MELI
      else if (gateway === PaymentGateway.meli) {
        bankResponse = await this.bankMeliService.paymentRequest(
          amount * 10,
          `${this.apiPaymentUrl}/meli/verify`,
          orderId,
          user.mobile,
          'اتوتیک',
        );
      }
      // SNAPP_PAY
      else if (
        gateway === PaymentGateway.snappPay &&
        [PaymentType.shop, PaymentType.service].includes(type)
      ) {
        businessPartnerCode = await this._generateBusinessPartnerCode();

        const data = await this.orderService.prepareSnappPayCartList(orderId);

        bankResponse = await this.snappPayService.paymentRequest(
          data.cartList,
          amount * 10,
          `https://auto-tik.com/snapp-pay`,
          businessPartnerCode,
          user.mobile,
          data.discountAmount,
          data.shippingAmount,
          data.externalSourceAmount,
        );
      } else {
        throw 'درخواست نامعتبر است!';
      }

      const { identifiers } = await this.paymentRepository
        .createQueryBuilder()
        .insert()
        .values({
          userId: user.id,
          businessPartnerCode,
          amount,
          authority: bankResponse.authority,
          usedWalletAmount,
          gateway: PaymentGatewayCode[gateway],
          status: PaymentStatus.pending,
          type,
          createdAt: new Date(),
          updatedAt: new Date(),
          ...([PaymentType.shop, PaymentType.service].includes(type) && {
            orderId,
          }),
        })
        .execute();

      return {
        paymentId: identifiers[0].id,
        bankUrl: bankResponse.url,
      };
    } catch (e) {
      console.log(e);
      this.error.internalServerError([
        'اتصال به بانک برقرار نشد، لطفا دوباره تلاش کنید!',
      ]);
    }
  }

  /**
   * --------------------------------------------------------
   * GET /payments/zarinpal/verify
   */
  async verifyZarinpal(authority: string, status: string) {
    const payment = await this.paymentRepository.findOne({
      where: {
        authority,
        status: PaymentStatus.pending,
      },
      relations: ['user'],
    });

    if (!payment) {
      return this.sitePaymentResultUrl('error', PaymentGateway.zarinpal);
    }

    if (status === 'NOK') {
      return this.sitePaymentResultUrl(
        'error',
        PaymentGateway.zarinpal,
        payment,
      );
    }

    // Checking the user wallet for usedWalletAmount
    const isNotValidWalletAmount = this.isNotValidWalletAmount(
      payment,
      PaymentGateway.zarinpal,
    );
    if (isNotValidWalletAmount) {
      return isNotValidWalletAmount;
    }

    // Verifying the request from the bank
    try {
      const result = await this.zarinpalService.paymentVerification(
        authority,
        payment.amount,
      );

      payment.referenceNumber = result?.referenceNumber || '';
      payment.status = PaymentStatus.done;
      payment.updatedAt = new Date();
      await payment.save();
    } catch (error) {
      return this.sitePaymentResultUrl(
        'error',
        PaymentGateway.zarinpal,
        payment,
      );
    }

    // Updating the user wallet by usedWalletAmount
    await this.userService.reduceBothWallets(
      payment.userId,
      payment.usedWalletAmount,
    );

    // Completing the payment if there is a task
    const { success, message, requestId } = await this.completeTask(payment);

    if (!success) {
      return this.sitePaymentResultUrl(
        'warning',
        PaymentGateway.zarinpal,
        payment,
        '',
        message,
      );
    }

    return this.sitePaymentResultUrl(
      'success',
      PaymentGateway.zarinpal,
      payment,
      requestId,
    );
  }

  /**
   * --------------------------------------------------------
   * GET /payments/meli/verify
   */
  async verifyMeli(authority: string, code: string) {
    console.log('MELI', 'VERIFY', { authority, code });

    const payment = await this.paymentRepository.findOne({
      where: {
        authority,
        status: PaymentStatus.pending,
      },
      relations: ['user'],
    });

    if (!payment) {
      return this.sitePaymentResultUrl('error', PaymentGateway.meli);
    }

    if (code !== '0') {
      return this.sitePaymentResultUrl('error', PaymentGateway.meli, payment);
    }

    // Checking the user wallet for usedWalletAmount
    const isNotValidWalletAmount = this.isNotValidWalletAmount(
      payment,
      PaymentGateway.meli,
    );
    if (isNotValidWalletAmount) {
      return isNotValidWalletAmount;
    }

    // Verifying the request from the bank
    const meliRes = await retryOperation(() =>
      this.bankMeliService.paymentVerification(authority),
    );

    if (!meliRes.success) {
      return this.sitePaymentResultUrl('error', PaymentGateway.meli, payment);
    }

    payment.referenceId = meliRes.result?.message || '';
    payment.referenceNumber = meliRes.result?.referenceNumber || '';
    payment.tranceNumber = meliRes.result?.tranceNumber || '';
    payment.updatedAt = new Date();
    payment.status = PaymentStatus.done;
    await payment.save();

    // Updating the user wallet by usedWalletAmount
    await this.userService.reduceBothWallets(
      payment.userId,
      payment.usedWalletAmount,
    );

    // Completing the payment if there is a task
    const { success, message, requestId } = await this.completeTask(payment);

    if (!success) {
      return this.sitePaymentResultUrl(
        'warning',
        PaymentGateway.meli,
        payment,
        '',
        message,
      );
    }

    return this.sitePaymentResultUrl(
      'success',
      PaymentGateway.zarinpal,
      payment,
      requestId,
    );
  }

  /**
   * --------------------------------------------------------
   * GET /payments/snapp-pay/verify
   */
  async verifySnappPay(businessPartnerCode: string, state: string) {
    const payment = await this.paymentRepository.findOne({
      where: {
        businessPartnerCode,
        status: PaymentStatus.pending,
      },
      relations: ['user'],
    });

    if (!payment) {
      return this.sitePaymentResultUrl('error', PaymentGateway.snappPay);
    }

    if (state === 'FAILED') {
      await retryOperation(() =>
        this.snappPayService.revertRequest(payment.authority),
      );

      return this.sitePaymentResultUrl(
        'error',
        PaymentGateway.snappPay,
        payment,
      );
    }

    // Checking the user wallet for usedWalletAmount
    const isNotValidWalletAmount = this.isNotValidWalletAmount(
      payment,
      PaymentGateway.snappPay,
    );
    if (isNotValidWalletAmount) {
      await retryOperation(() =>
        this.snappPayService.revertRequest(payment.authority),
      );
      return isNotValidWalletAmount;
    }

    // Verifying the request from the bank
    const verifyRes = await retryOperation(() =>
      this.snappPayService.paymentVerification(payment.authority),
    );

    if (!verifyRes.success) {
      return this.sitePaymentResultUrl(
        'error',
        PaymentGateway.snappPay,
        payment,
      );
    }

    const settleRes = await retryOperation(() =>
      this.snappPayService.settleRequest(payment.authority),
    );

    if (!settleRes.success) {
      return this.sitePaymentResultUrl(
        'error',
        PaymentGateway.snappPay,
        payment,
      );
    }

    payment.status = PaymentStatus.done;
    payment.updatedAt = new Date();
    await payment.save();

    // Updating the user wallet by usedWalletAmount
    await this.userService.reduceBothWallets(
      payment.userId,
      payment.usedWalletAmount,
    );

    // Completing the payment if there is a task
    const { success, message, requestId } = await this.completeTask(payment);

    if (!success) {
      return this.sitePaymentResultUrl(
        'warning',
        PaymentGateway.snappPay,
        payment,
        '',
        message,
      );
    }

    return this.sitePaymentResultUrl(
      'success',
      PaymentGateway.snappPay,
      payment,
      requestId,
    );
  }

  /**
   * --------------------------------------------------------
   * retry 3 times to getting the inquiry
   */
  private async completeTask(payment: PaymentEntity = null, retry = 1) {
    const result = { success: true, message: '', requestId: '' };

    try {
      // ---------
      if ([PaymentType.shop, PaymentType.service].includes(payment.type)) {
        const snappPayPaymentToken =
          payment.gateway === PaymentGatewayCode.snapp_pay
            ? payment.authority
            : null;

        await this.orderService.paidByUser(
          payment.orderId,
          snappPayPaymentToken,
        );
        result.requestId = `${payment.orderId}`;
      }
      // ---------
      if (payment.type === PaymentType.offense_inquiry) {
        const data = await this.drivingOffenseService.doInquiry(payment.id);
        result.requestId = `${data.id}`;
      }
      // ---------
      // else if (payment.type === PaymentType.offense_pay) {
      //   const data = await this.drivingOffenseService.doPaying(payment.id);
      //   result.requestId = `${data.id}`;
      // }
      // ---------
      else if (payment.type === PaymentType.negative_inquiry) {
        const data = await this.negativeScoreService.doInquiry(payment.id);
        result.requestId = `${data.id}`;
      }
      // ---------
      else if (payment.type === PaymentType.freeway_inquiry) {
        const data = await this.freewayTollService.doInquiry(payment.id);
        result.requestId = `${data.id}`;
      }
    } catch (e) {
      console.log('complete task', e);

      if (retry < 3) {
        return await this.completeTask(payment, retry + 1);
      }

      if (
        [
          PaymentType.offense_inquiry,
          PaymentType.negative_inquiry,
          PaymentType.freeway_inquiry,
        ].includes(payment.type)
      ) {
        // Updating the user wallet
        await this.userService.updateWallet(
          payment.userId,
          payment.amount + (payment.usedWalletAmount || 0),
        );
        result.message =
          'پرداخت شما با موفقیت انجام شده است ولی در ادامه فرآیند سیستم خطایی رخ داده است، مبلغ پرداختی به کیف پول شما واریز شد، می‌توانید ازین اعتبار برای استعلام مجدد استفاده نمایید';
      } else {
        result.message =
          'پرداخت شما با موفقیت انجام شده است ولی در ادامه فرآیند سیستم خطایی رخ داده است، لطفا با پشتیبانی 09391857423 تماس حاصل نمایید';
      }

      result.success = false;
    }

    return result;
  }

  /**
   * --------------------------------------------------------
   * Checking the user wallet for usedWalletAmount
   */
  private isNotValidWalletAmount(
    payment: PaymentEntity,
    gateway: PaymentGateway,
  ) {
    const totalWallet = payment.user.realWallet + payment.user.virtualWallet;
    if (payment.usedWalletAmount && payment.usedWalletAmount > totalWallet) {
      return this.sitePaymentResultUrl(
        'warning',
        gateway,
        payment,
        '',
        'اعتبار کیف پول شما برای تکمیل مبلغ پرداخت کافی نمی‌باشد، مبلغ مورد نظر تا 24 ساعت آینده به حساب شما عودت داده می‌شود',
      );
    }

    return false;
  }

  /**
   * --------------------------------------------------------
   */
  private sitePaymentResultUrl(
    status: 'success' | 'error' | 'warning',
    gateway: PaymentGateway,
    payment: PaymentEntity = null,
    requestId = '',
    customMessage = '',
  ) {
    let message =
      'در هنگام تایید پرداخت خطایی رخ داده است، مبلغ مورد نظر تا 24 ساعت آینده به حساب شما عودت داده می‌شود';
    if (status === 'success') {
      message = 'پرداخت با موفقیت انجام شد';
    }

    if (customMessage) message = customMessage;

    return `${this.sitePaymentResult}?Status=${status}&Payment=${
      payment?.id || ''
    }&RequestType=${
      payment?.type || ''
    }&RequestId=${requestId}&Type=${gateway}&Message=${message}&Client=${
      payment?.clientType || ''
    }`;
  }

  /**
   * -------------------------------------------------------
   */
  private async _generateBusinessPartnerCode() {
    let code;

    do {
      code = `ATP${Math.floor(1000000 + Math.random() * 9000000)}`;
    } while (
      (await this.paymentRepository.count({ businessPartnerCode: code })) > 0
    );

    return code;
  }
}
