package com.braintree.facade.backoffice.impl;

import com.braintree.command.result.BrainTreeVoidResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.facade.backoffice.BraintreeBackofficeVoidFacade;
import com.braintree.helpers.OrderVoidHelper;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.order.service.BraintreeOrderBackofficeUtilService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintree.util.BrainTreeUtils;
import com.braintreegateway.Transaction;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.ordermanagementfacades.payment.data.PaymentTransactionEntryData;
import de.hybris.platform.payment.commands.request.VoidRequest;
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.servicelayer.dto.converter.Converter;
import de.hybris.platform.servicelayer.model.ModelService;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

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

public class DefaultBraintreeBackofficeVoidFacade implements BraintreeBackofficeVoidFacade {

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

    private BrainTreePaymentService brainTreePaymentService;
    private ModelService modelService;
    private BrainTreeConfigService brainTreeConfigService;
    private BraintreeOrderBackofficeUtilService braintreeOrderBackofficeUtilService;
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;
    private BrainTreeTransactionService brainTreeTransactionService;
    private OrderVoidHelper orderVoidHelper;

    @Override
    public boolean isVoidPossible(final OrderModel order) {
        if (null == order) {
            LOG.warn("OrderModel is null");
            return false;
        }
        LOG.info("isVoidPossible, order.getTotalPrice: " + order.getTotalPrice());

        modelService.refresh(order);
        boolean isVoidPossible = false;
        boolean settlementConfigParameter = brainTreeConfigService.isSubmitForSettlement();

        if (order.getPaymentInfo() instanceof BrainTreePaymentInfoModel) {
            final String paymentProvider = ((BrainTreePaymentInfoModel) order.getPaymentInfo()).getPaymentProvider();
            final String intent = ((BrainTreePaymentInfoModel) order.getPaymentInfo()).getPayPalIntent();

            if (braintreeOrderBackofficeUtilService.paidByCard(order) || BrainTreeUtils.isVenmoPayment(paymentProvider)) {
                if (!settlementConfigParameter && !braintreeOrderBackofficeUtilService.isOrderVoided(order)
                        && !braintreeOrderBackofficeUtilService.isOrderAmountCaptured(order) && !isOrderCaptureOrPartialCapture(order)) {
                    isVoidPossible = true;
                }
            } else if (!braintreeOrderBackofficeUtilService.isOrderVoided(order) && !braintreeOrderBackofficeUtilService.isOrderAmountCaptured(order)) {
                if (BrainTreeUtils.isIntentAuthorize(intent)) {
                    isVoidPossible = checkOrderAndSetVoidPossible(order, isVoidPossible);
                } else {
                    LOG.warn("Order was placed with incorrect intent = '" + intent);
                }
            }
        }
        return isVoidPossible;
    }

    private boolean checkOrderAndSetVoidPossible(OrderModel order, boolean isVoidPossible) {
        if (!isOrderCaptureOrPartialCapture(order)) {
            isVoidPossible = isVoidAuthorizationPossible(order);
        }
        return isVoidPossible;
    }

    @Override
    public List<PaymentTransactionEntryModel> getVoidableTransactions(final OrderModel order) {
        List<PaymentTransactionEntryModel> result = new ArrayList<>();

        for (PaymentTransactionModel transaction : order.getPaymentTransactions()) {
            for (PaymentTransactionEntryModel paymentEntry : transaction.getEntries()) {
                if (TransactionStatus.ACCEPTED.name().equals(paymentEntry.getTransactionStatus()) &&
                        PaymentTransactionType.AUTHORIZATION.equals(paymentEntry.getType())) {
                    result.add(paymentEntry);
                }
            }
        }

        return result;
    }

    @Override
    public PaymentTransactionEntryData executeVoid(final PaymentTransactionEntryModel transaction)
            throws BraintreeErrorException {
        final VoidRequest voidRequest = new VoidRequest("-not-used-", transaction.getRequestId(), StringUtils.EMPTY,
                StringUtils.EMPTY);
        final BrainTreeVoidResult voidResult = brainTreePaymentService.voidTransaction(voidRequest);
        brainTreeTransactionService.addTransactionLog(transaction.getPaymentTransaction(), voidResult,
                BRAINTREE_INTERACTION_TYPE_VOID);
        if (voidResult.isSuccess()) {
            brainTreeTransactionService.saveBraintreeTransactionStatus(voidResult.getStatus(),
                    transaction.getPaymentTransaction());
            return handleSuccessfulVoid(transaction);
        } else {
            LOG.error("Error, message: " + voidResult.getErrorMessage());
            throw new BraintreeErrorException(voidResult.getErrorMessage(), voidResult.getTransactionId());
        }
    }

    private PaymentTransactionEntryData handleSuccessfulVoid(final PaymentTransactionEntryModel transaction) {
        OrderModel order = (OrderModel) transaction.getPaymentTransaction().getOrder();
        transaction.setTransactionStatus(Transaction.Status.VOIDED.name());
        order.setStatus(OrderStatus.CANCELLED);
        modelService.save(transaction);
        modelService.refresh(transaction);
        modelService.save(order);
        modelService.refresh(order);
        return ordermanagementPaymentTransactionEntryConverter.convert(transaction);
    }

    @Override
    public boolean isVoidAuthorizationPossible(final OrderModel order) {
        if (order != null) {
            return isSuccessfulTransactionPresent(order, PaymentTransactionType.AUTHORIZATION)
                    || isSuccessfulTransactionPresent(order, PaymentTransactionType.ORDER);
        }
        return false;
    }

    private boolean isSuccessfulTransactionPresent(final OrderModel order,
                                                   final PaymentTransactionType transactionType) {
        for (PaymentTransactionModel paymentTransaction : order.getPaymentTransactions()) {
            for (PaymentTransactionEntryModel transactionEntry : paymentTransaction.getEntries()) {
                if (transactionType.equals(transactionEntry.getType())
                        && transactionEntry.getTransactionStatusDetails().startsWith("SUCCESFULL")
                        && TransactionStatus.ACCEPTED.name().equals(transactionEntry.getTransactionStatus())) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isOrderCaptureOrPartialCapture(OrderModel order) {
        boolean isOrderCapture = false;
        for (PaymentTransactionModel paymentTransaction : order.getPaymentTransactions()) {
            for (PaymentTransactionEntryModel transactionEntry : paymentTransaction.getEntries()) {
                if (orderVoidHelper.isSuccessfulCaptureTransaction(transactionEntry)) {
                    isOrderCapture = true;
                }
            }
        }
        return isOrderCapture;
    }

    public void setBrainTreePaymentService(BrainTreePaymentService brainTreePaymentService) {
        this.brainTreePaymentService = brainTreePaymentService;
    }

    public void setModelService(ModelService modelService) {
        this.modelService = modelService;
    }

    public void setBrainTreeConfigService(BrainTreeConfigService brainTreeConfigService) {
        this.brainTreeConfigService = brainTreeConfigService;
    }

    public void setBraintreeOrderBackofficeUtilService(BraintreeOrderBackofficeUtilService braintreeOrderBackofficeUtilService) {
        this.braintreeOrderBackofficeUtilService = braintreeOrderBackofficeUtilService;
    }

    public void setOrdermanagementPaymentTransactionEntryConverter(Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter) {
        this.ordermanagementPaymentTransactionEntryConverter = ordermanagementPaymentTransactionEntryConverter;
    }

    public void setBrainTreeTransactionService(BrainTreeTransactionService brainTreeTransactionService) {
        this.brainTreeTransactionService = brainTreeTransactionService;
    }

    public void setOrderVoidHelper(OrderVoidHelper orderVoidHelper) {
        this.orderVoidHelper = orderVoidHelper;
    }

}
