package com.braintree.actions.order;

import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.util.BrainTreeUtils;
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.orderprocessing.model.OrderProcessModel;
import de.hybris.platform.payment.PaymentService;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
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.AbstractAction;
import de.hybris.platform.servicelayer.session.SessionExecutionBody;
import de.hybris.platform.servicelayer.session.SessionService;
import de.hybris.platform.task.RetryLaterException;
import org.apache.log4j.Logger;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;


public class BrainTreeTakePaymentAction extends AbstractAction<OrderProcessModel> {

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

    public enum Transition {
        OK, NOK, UNDEFINED;

        public static Set<String> getStringValues() {
            final Set<String> res = new HashSet<String>();
            for (final Transition transitions : Transition.values()) {
                res.add(transitions.name());
            }
            return res;
        }
    }

    private static final String CURRENT_SITE = "currentSite";

    private PaymentService paymentService;
    private SessionService sessionService;

    @Override
    public Set<String> getTransitions() {
        return Transition.getStringValues();
    }

    @Override
    public final String execute(final OrderProcessModel process) throws RetryLaterException, Exception {
        return executeAction(process).toString();
    }

    protected Transition executeAction(final OrderProcessModel process) {
        return sessionService.executeInLocalViewWithParams(Map.of(CURRENT_SITE, process.getOrder().getSite()), new SessionExecutionBody() {
            @Override
            public Object execute() {
                final OrderModel order = process.getOrder();

                LOG.info("isCaptureRequired(order): " + isCaptureRequired(order));

                if (isCaptureRequired(order)) {
                    for (final PaymentTransactionModel txn : order.getPaymentTransactions()) {
                        if (isNoCaptureAvailable(txn) && isAcceptedAuthorizationPresent(txn)) {
                            final PaymentTransactionEntryModel txnEntry = paymentService.capture(txn);
                            if (TransactionStatus.ACCEPTED.name().equals(txnEntry.getTransactionStatus())) {
                                LOG.info("The payment transaction has been captured. Order: " + order.getCode() + ". Txn: " + txn.getCode());
                                setOrderStatus(order, OrderStatus.PAYMENT_CAPTURED);
                            } else if (TransactionStatus.REVIEW.name().equals(txnEntry.getTransactionStatus())) {
                                setOrderStatus(order, OrderStatus.PARTIAL_CAPTURE);
                                LOG.info("The payment transaction should to be captured. Order: " + order.getCode() + ". Txn: " + txn.getCode());
                                return Transition.UNDEFINED;
                            } else if (checkForInvalidCaptureTransactions(txnEntry)) {
                                LOG.warn("The payment transaction capture has failed. Order: " + order.getCode() + ". Txn: " + txn.getCode() + ". " +
                                        "Waiting for submitForSettlement action.");
                                return Transition.UNDEFINED;
                            } else {
                                LOG.warn("The payment transaction capture has failed. Order: " + order.getCode() + ". Txn: " + txn
                                        .getCode());
                                setOrderStatus(order, OrderStatus.PAYMENT_NOT_CAPTURED);
                                return Transition.NOK;
                            }
                        }
                    }
                } else {
                    LOG.info("Return result as: " + Transition.UNDEFINED);
                    return Transition.UNDEFINED;
                }
                LOG.info("Return result as: " + Transition.OK);
                return Transition.OK;
            }
        });
    }

    private boolean isNoCaptureAvailable(PaymentTransactionModel txn) {
        return txn.getEntries().stream()
                .noneMatch(entry -> (PaymentTransactionType.CAPTURE.equals(entry.getType())
                        || PaymentTransactionType.PARTIAL_CAPTURE.equals(entry.getType()))
                        && TransactionStatus.ACCEPTED.name().equals(entry.getTransactionStatus())
                );
    }

    private boolean isAcceptedAuthorizationPresent(PaymentTransactionModel txn) {
        return txn.getEntries().stream()
                .anyMatch(entry -> PaymentTransactionType.AUTHORIZATION.equals(entry.getType())
                        && TransactionStatus.ACCEPTED.name().equals(entry.getTransactionStatus())
                );
    }

    private boolean checkForInvalidCaptureTransactions(PaymentTransactionEntryModel transactionEntry) {
        return TransactionStatus.REJECTED.name().equals(transactionEntry.getTransactionStatus())
                && !TransactionStatusDetails.BANK_DECLINE.name().equals(transactionEntry.getTransactionStatusDetails());
    }

    private boolean isCaptureRequired(final OrderModel order) {

        if (order.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            final String paymentProvider = ((BrainTreePaymentInfoModel) order.getPaymentInfo()).getPaymentProvider();
            if (!BrainTreeUtils.isPayPalPayment(paymentProvider)) {
                return true;
            }
            final String intent = ((BrainTreePaymentInfoModel) order.getPaymentInfo()).getPayPalIntent();
            if (BrainTreeUtils.isIntentSale(intent)) {
                return true;
            }
        }
        return order.getPaymentInfo() instanceof CreditCardPaymentInfoModel
                || order.getPaymentInfo() instanceof BrainTreePaymentInfoModel;
    }

    public void setPaymentService(final PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void setSessionService(SessionService sessionService) {
        this.sessionService = sessionService;
    }

}
