class AdyenSubscriber {
  constructor(container) {
    /** @private @const {CartService} */
    this.cartService_ = container.cartService

    /** @private @const {OrderService} */
    this.orderService_ = container.orderService

    /** @private @const {EventBusService} */
    this.eventBus_ = container.eventBusService

    /** @private @const {PaymentRepository} */
    this.paymentRepository_ = container.paymentRepository

    this.manager_ = container.manager

    this.eventBus_.subscribe(
      "adyen.notification_received",
      async (notification) => this.handleAdyenNotification(notification)
    )
  }

  /**
   * Webhook handler for Adyen payment.
   * @param {object} notification - webhook notification object
   * @returns {string} the status of the payment intent
   */
  async handleAdyenNotification(notification) {
    switch (true) {
      case notification.success === "true" &&
        notification.eventCode === "AUTHORISATION": {
        this.handleAuthorization_(notification)
        break
      }
      case notification.success === "false" &&
        notification.eventCode === "AUTHORISATION": {
        this.handleFailedAuthorization_(notification)
        break
      }
      case notification.success === "true" &&
        notification.eventCode === "CAPTURE_FAILED": {
        this.handleFailedCapture_(notification)
        break
      }
      case notification.success === "false" &&
        notification.eventCode === "CAPTURE": {
        this.handleFailedCapture_(notification)
        break
      }
      case notification.success === "false" &&
        notification.eventCode === "REFUND": {
        this.handleFailedRefund_(notification)
        break
      }
      default:
        break
    }
  }

  async handleFailedAuthorization_(notification) {
    const cartId = notification.additionalData["metadata.cart_id"]
    const cart = await this.cartService_.retrieve(cartId, {
      relations: ["payment_sessions"],
    })

    const { payment_session } = cart

    const updated = {
      ...payment_session,
      status: "error",
      data: {
        ...payment_session.data,
        pspReference: notification.pspReference,
      },
    }

    await this.cartService_.updatePaymentSession(cart.id, updated)
  }

  async handleAuthorization_(notification) {
    const cartId = notification.additionalData["metadata.cart_id"]
    const paymentRepository = this.manager_.getCustomRepository(
      this.paymentRepository_
    )

    // We need to ensure, that an order is created in situations, where the
    // customer might have closed their browser prior to order creation
    try {
      const orderPayment = await paymentRepository.findOne({
        where: { cart_id: cartId },
      })

      if (!orderPayment) {
        throw new Error("Payment not found")
      }

      const updatedPayment = {
        ...orderPayment,
        data: {
          ...orderPayment.data,
          resultCode: "Authorised",
          pspReference: notification.pspReference,
        },
      }

      await this.paymentRepository_.save(updatedPayment)
    } catch (error) {
      console.log(error)
      await this.manager_.transaction(async (manager) => {
        const session = {
          pspReference: notification.pspReference,
          resultCode: "Authorised",
        }

        await this.cartService_
          .withTransaction(manager)
          .updatePaymentSession(cartId, session)

        await this.cartService_
          .withTransaction(manager)
          .authorizePayment(cartId)

        await this.orderService_.withTransaction(manager).createFromCart(cartId)
      })
    }
  }

  async handleFailedCapture_(notification) {
    const cartId = notification.additionalData["metadata.cart_id"]

    const order = await this.orderService_.retrieveByCartId(cartId)

    await this.orderService_.update(order.id, {
      payment_status: "requires_action",
    })

    await this.eventBus_.emit("order.payment_capture_failed", {
      order,
      error: new Error(`Adyen payment capture: ${notification.reason}`),
    })
  }

  async handleFailedRefund_(notification) {
    const cartId = notification.additionalData["metadata.cart_id"]

    const order = await this.orderService_.retrieveByCartId(cartId)

    await this.orderService_.update(order.id, {
      payment_status: "requires_action",
    })

    await this.eventBus_.emit("order.refund_failed", {
      order,
      error: new Error(`Adyen payment capture: ${notification.reason}`),
    })
  }
}

export default AdyenSubscriber
