package com.braintree.actions.order;

import static com.braintree.constants.BraintreeConstants.FAKE_REQUEST_ID;

import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintree.util.BrainTreeUtils;
import de.hybris.platform.commerceservices.service.data.CommerceOrderParameter;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.CreditCardPaymentInfoModel;
import de.hybris.platform.core.model.order.payment.InvoicePaymentInfoModel;
import de.hybris.platform.orderprocessing.model.OrderProcessModel;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.enums.PaymentTransactionType;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.processengine.action.AbstractSimpleDecisionAction;
import java.util.Collection;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * This action implements payment authorization using {@link CreditCardPaymentInfoModel}. Any other payment model could
 * be implemented here, or in a separate action, if the process flow differs.
 */
public class BrainTreeCheckAuthorizeOrderPaymentAction extends AbstractSimpleDecisionAction<OrderProcessModel> {

    private static final Logger LOG = Logger.getLogger(BrainTreeCheckAuthorizeOrderPaymentAction.class);

    @Autowired
    private BrainTreeTransactionService brainTreeTransactionService;

    @Autowired
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;

    @Override
    public Transition executeAction(final OrderProcessModel process) {
        final OrderModel order = process.getOrder();

        if (order != null) {
            if (order.getPaymentInfo() instanceof InvoicePaymentInfoModel) {
                return Transition.OK;
            } else {
                return assignStatusForOrder(order);
            }
        }
        return Transition.NOK;
    }

    /**
     * Sets the status for given order in case on of its {@link PaymentTransactionEntryModel} matches proper {@link
     * PaymentTransactionType} and {@link TransactionStatus}.
     *
     * @param order {@link OrderModel}
     * @return {@link Transition}
     */
    protected Transition assignStatusForOrder(final OrderModel order) {

        if (order.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            BrainTreePaymentInfoModel paymentInfoModel = (BrainTreePaymentInfoModel) order.getPaymentInfo();
            String intent = paymentInfoModel.getPayPalIntent();
            LOG.info("intent: " + intent);
            String cardNumber = paymentInfoModel.getCardNumber();
            LOG.info("cardNumber: " + cardNumber);

            final boolean isNoTransactionAuthorized = isNoTransactionAuthorized(order);
            final boolean isVenmoPayment = BrainTreeUtils.isVenmoPayment(paymentInfoModel.getPaymentProvider());
            final CommerceOrderParameter parameter = new CommerceOrderParameter();
            parameter.setOrder(order);

            if (isNotBlankOrIsVenmoPaymentAndIsNoTransactionAuthorized(cardNumber, isVenmoPayment, isNoTransactionAuthorized)) {
                brainTreeTransactionService.createAuthorizationTransaction(parameter);
                LOG.info("return result: " + Transition.OK);
                return Transition.OK;
            }

            if (isNoTransactionAuthorized) {
                if (BrainTreeUtils.isIntentAuthorize(intent)) {
                    brainTreeTransactionService.createAuthorizationTransaction(parameter);
                    return Transition.OK;
                } else if (BrainTreeUtils.isIntentSale(intent)) {
                    brainTreeTransactionService.createAuthorizationTransaction(parameter);
                }
            }
        }

        for (final PaymentTransactionModel transaction : order.getPaymentTransactions()) {
            for (final PaymentTransactionEntryModel entry : transaction.getEntries()) {
                if (isAuthorizationTransactionTypeAndAcceptedTransactionStatus(entry)) {
                    order.setStatus(OrderStatus.PAYMENT_AUTHORIZED);
                    modelService.save(order);
                    return Transition.OK;
                }
            }
        }
        LOG.error("Return from action with result: " + Transition.NOK);
        return Transition.NOK;
    }

    private boolean isAuthorizationTransactionTypeAndAcceptedTransactionStatus(PaymentTransactionEntryModel entry) {
        return PaymentTransactionType.AUTHORIZATION.equals(entry.getType())
                && TransactionStatus.ACCEPTED.name().equals(entry.getTransactionStatus());
    }

    private boolean isNotBlankOrIsVenmoPaymentAndIsNoTransactionAuthorized(String cardNumber, boolean isVenmoPayment,
                                                                           boolean isNoTransactionAuthorized) {
        return (StringUtils.isNotBlank(cardNumber) || isVenmoPayment) && isNoTransactionAuthorized;
    }

    private boolean isNoTransactionAuthorized(final OrderModel order) {
        return order.getPaymentTransactions().stream()
            .map(PaymentTransactionModel::getEntries)
            .flatMap(Collection::stream)
            .filter(isAuthorizationTypePredicate())
            .filter(isFakeTransactionPredicate().negate())
            .map(PaymentTransactionEntryModel::getTransactionStatus)
            .noneMatch(Predicate.isEqual(TransactionStatus.ACCEPTED.name()));
    }

    private Predicate<PaymentTransactionEntryModel> isAuthorizationTypePredicate() {
        return entry -> PaymentTransactionType.AUTHORIZATION.equals(entry.getType());
    }

    private Predicate<PaymentTransactionEntryModel> isFakeTransactionPredicate() {
        return entry -> FAKE_REQUEST_ID.equals(entry.getRequestId());
    }
}
