package com.red.circle.order.app.command.pay.web;

import com.red.circle.component.pay.paypal.enums.PayPalNoticeTypeEnum;
import com.red.circle.component.pay.paypal.notice.PaypalNotice;
import com.red.circle.component.pay.paypal.notice.PaypalNoticePurchaseUnits;
import com.red.circle.component.pay.paypal.notice.PaypalNoticeResource;
import com.red.circle.component.pay.paypal.service.PaypalService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.order.app.common.InAppPurchaseCommon;
import com.red.circle.order.app.common.RecycleReimburseOrderProductCommon;
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService;
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice;
import com.red.circle.order.inner.asserts.OrderErrorCode;
import com.red.circle.order.inner.asserts.PayErrorCode;
import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.json.JacksonUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * paypal 通知.
 * <p>
 * CHECKOUT.ORDER.APPROVED 客户授权支付，此时可收款。
 * <p>
 * PAYMENT.CAPTURE.COMPLETED 收款完成，即交易完成。
 * <p>
 * PAYMENT.CAPTURE.PENDING 交易需要审核。
 * <p>
 * PAYMENT.CAPTURE.DENIED 拒绝了收款。
 * <p>
 * PAYMENT.CAPTURE.REFUNDED 退款。
 * <p>
 * https://developer.paypal.com/docs/api/orders/v2/#orders_create
 *
 * @author pengliang on 2022/8/1
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PayPalServerNoticeCmdExe {

  private final PaypalService paypalService;
  private final InAppPurchaseCommon inAppPurchaseCommon;
  private final InAppPurchaseDetailsService inAppPurchaseDetailsService;
  private final RecycleReimburseOrderProductCommon recycleReimburseOrderProductCommon;


  public void execute(byte[] body) {

    ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        Objects.nonNull(body) && body.length > 0);

    log.warn("PaypalNotice:{}", new String(body, StandardCharsets.UTF_8));

    PaypalNotice noticeBody = PaypalNotice.toSerializable(body);

    if (Objects.isNull(noticeBody)) {
      log.error("Paypal通知解析错误:{}", new String(body, StandardCharsets.UTF_8));
      ResponseAssert.failure(ResponseErrorCode.INTERNAL_SERVER_ERROR);
    }

    // 校验通知是否真实存在
    PaypalNotice notice = JacksonUtils.readValue(paypalService.checkNotice(noticeBody.getId()),
        PaypalNotice.class);
    ResponseAssert.notNull(ResponseErrorCode.NOT_ACCEPTABLE, notice);
    ResponseAssert.isTrue(ResponseErrorCode.NOT_ACCEPTABLE,
        Objects.equals(notice.getEventType(), noticeBody.getEventType()));

    // 客户已授权付款,商家可以进行收款.
    if (notice.eventCheckoutOrderApproved()) {
      authorizationAndCapture(notice);
      return;
    }

    // 支付完成通知
    if (notice.eventPaymentCaptureCompleted()) {
      purchaseComplete(notice);
      return;
    }

    // 退款通知
    if (notice.eventPaymentCaptureRefunded()) {
      refundComplete(notice);
      return;
    }

    // 交易被审核通知
    if (notice.eventPaymentCapturePending()) {
      pend(notice);
      return;
    }

    // 拒绝收款通知
    if (notice.eventPaymentCaptureDenied()) {
      log.error("PayPal - 拒绝收款通知:{}", new String(body, StandardCharsets.UTF_8));
    }

  }

  /**
   * 客户授权购买并且捕获该订单.
   */
  private void authorizationAndCapture(PaypalNotice notice) {
    // 获取商品信息.
    PaypalNoticeResource resource = notice.getResource();
    ResponseAssert.notNull(ResponseErrorCode.NOT_ACCEPTABLE, resource);
    PaypalNoticePurchaseUnits purchaseUnits = resource.getPurchaseUnits().get(0);
    ResponseAssert.isTrue(ResponseErrorCode.NOT_ACCEPTABLE,
        Objects.nonNull(purchaseUnits) && StringUtils.isNotBlank(purchaseUnits.getCustomId()));

    // 获得数据库订单
    InAppPurchaseDetails order = inAppPurchaseDetails(notice, purchaseUnits.getCustomId());

    if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) {
      log.warn("paypal 订单已处理.");
      ResponseAssert.failure(PayErrorCode.REPEATED_NOTICE);
    }

    inAppPurchaseDetailsService.addMetadata(order.getId(),
        notice.checkoutOrderApprovedNoField(),
        notice.getResource().getId());

    // 捕获已付款订单（捕获之后Paypal会发 [购买完成] 通知给我们服务器）
    paypalService.capturePaymentOrder(order.getOrderId());
  }

  /**
   * 购买完成.
   */
  private void purchaseComplete(PaypalNotice notice) {

    // 获得数据库订单
    InAppPurchaseDetails order = inAppPurchaseDetails(notice, notice.getResource().getCustomId());

    if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) {
      log.warn("paypal 订单已处理.");
      ResponseAssert.failure(PayErrorCode.REPEATED_NOTICE);
    }

    // 校验订单有效性: 必须为客户授权状态
    ResponseAssert.isTrue(ResponseErrorCode.NOT_ACCEPTABLE,
        order.getPayNotices().stream().anyMatch(item -> Objects.equals(item.getNoticeType(),
            PayPalNoticeTypeEnum.CHECKOUT_ORDER_APPROVED.getName())
            || Objects.equals(item.getNoticeType(),
            PayPalNoticeTypeEnum.PAYMENT_CAPTURE_PENDING.getName())));

    inAppPurchaseDetailsService.addMetadata(order.getId(),
        notice.paymentCaptureCompletedNoField(),
        notice.getResource().getId());

    // 收款单
    if (order.receiptTypeEqReceipt()) {
      inAppPurchaseCommon.inAppPurchaseReceipt(order);
      return;
    }

    // 付款
    if (order.receiptTypeEqPayment()) {
      inAppPurchaseCommon.inAppPurchasePayment(order);
    }

    throw new IllegalArgumentException("Document type error, out of scope");
  }

  /**
   * 交易被PayPal审查.
   */
  private void pend(PaypalNotice notice) {

    InAppPurchaseDetails order = inAppPurchaseDetails(notice, notice.getResource().getCustomId());

    if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) {
      log.warn("paypal 订单已处理.");
      ResponseAssert.failure(PayErrorCode.REPEATED_NOTICE);
    }

    // 验证用户是否已授权.
    ResponseAssert.isTrue(ResponseErrorCode.NOT_ACCEPTABLE,
        order.getPayNotices().stream().anyMatch(item -> Objects.equals(item.getNoticeType(),
            PayPalNoticeTypeEnum.CHECKOUT_ORDER_APPROVED.getName())));

    inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.HANG);
  }

  /**
   * 退款完成.
   */
  private void refundComplete(PaypalNotice notice) {
    InAppPurchaseDetails order = inAppPurchaseDetails(notice, notice.getResource().getCustomId());

    ResponseAssert.isTrue(OrderErrorCode.VALIDATION_ORDER_FAILED,
        notice.getResource().getLinks().stream()
            .anyMatch(link ->
                Objects.nonNull(order.getMetadata())
                    && Objects.nonNull(
                    order.getMetadata().get(notice.paymentCaptureCompletedNoField()))
                    && link.getHref()
                    .contains(order.getMetadata().get(notice.paymentCaptureCompletedNoField()))));

    ResponseAssert.isFalse(PayErrorCode.REPEATED_NOTICE,
        Objects.equals(order.getStatus(), InAppPurchaseStatus.REFUND));

    inAppPurchaseDetailsService.addMetadata(order.getId(),
        notice.paymentCaptureRefundedNoField(),
        notice.getResource().getId());

    recycleReimburseOrderProductCommon.recycleAppOrderId(order.getId());
  }

  private InAppPurchaseDetails inAppPurchaseDetails(PaypalNotice notice, String customId) {
    // 获得数据库订单
    InAppPurchaseDetails order = inAppPurchaseDetailsService.getById(customId);

    if (Objects.isNull(order)) {
      log.warn("paypal 没有找到订单信息:{}", JacksonUtils.toJson(notice));
      ResponseAssert.failure(ResponseErrorCode.NOT_ACCEPTABLE, "not create order");
    }

    inAppPurchaseDetailsService.addPayNotices(order.getId(),
        new InAppPurchaseEventNotice()
            .setEventId(notice.getId())
            .setNoticeType(notice.getEventType())
            .setNoticeData(notice)
            .setCreateTime(TimestampUtils.now())
    );
    return order;
  }

}
