package com.braintree.facade.backoffice.impl;

import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.facade.backoffice.BraintreeBackofficeMultiCaptureFacade;
import com.braintree.order.capture.partial.services.BraintreePartialCaptureService;
import com.braintree.order.submitForSettlement.service.BraintreeSubmitForSettlementService;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
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.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 org.apache.log4j.Logger;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

public class DefaultBraintreeBackofficeMultiCaptureFacade implements BraintreeBackofficeMultiCaptureFacade {

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

    private BraintreePartialCaptureService braintreePartialCaptureService;
    private BraintreeSubmitForSettlementService braintreeSubmitForSettlementService;
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;

    @Override
    public boolean isMultiCapturePossible(final OrderModel order) {
        if (null == order) {
            LOG.warn("OrderModel is null");
            return false;
        }

        return !OrderStatus.COMPLETED.equals(order.getStatus())
                && !OrderStatus.CANCELLED.equals(order.getStatus())
                && isAnyAuthorizationAvailableForCapture(order);
    }

    private boolean isAnyAuthorizationAvailableForCapture(OrderModel order) {
        return order.getPaymentTransactions().stream()
                .map(PaymentTransactionModel::getEntries)
                .flatMap(List::stream)
                .filter(getAuthorizeTransactionTypePredicate())
                .anyMatch(entry -> !brainTreePaymentTransactionService.isOrderFullyCaptured(entry));
    }

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

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

        return result;
    }

    @Override
    public PaymentTransactionEntryData partialCapture(OrderModel orderModel, BigDecimal captureAmount,
                                                      final PaymentTransactionEntryModel entry, boolean isOrderToBeForceCompleted) throws BraintreeErrorException {
        return ordermanagementPaymentTransactionEntryConverter
                .convert(braintreePartialCaptureService.partialCapture(orderModel, captureAmount, entry, isOrderToBeForceCompleted));
    }

    @Override
    public BigDecimal getPossibleAmountForCapture(OrderModel orderModel) {
        return braintreePartialCaptureService.getPossibleAmountForCapture(orderModel);
    }

    @Override
    public boolean isPartialCapturePossible(OrderModel orderModel) {
        return braintreePartialCaptureService.isPartialCapturePossible(orderModel);
    }

    public BigDecimal getTransactionAmount(final PaymentTransactionEntryModel currentModel,
                                           final OrderModel orderModel) {
        return braintreePartialCaptureService.getTransactionAmount(currentModel, orderModel);
    }

    @Override
    public PaymentTransactionEntryData submitForSettlement(OrderModel orderModel, BigDecimal amount,
                                                           PaymentTransactionEntryModel entry, boolean isOrderToBeForceCompleted)
            throws BraintreeErrorException {
        return ordermanagementPaymentTransactionEntryConverter
                .convert(
                        braintreeSubmitForSettlementService.submitForSettlement(orderModel, amount, entry, isOrderToBeForceCompleted));
    }

    @Override
    public BigDecimal getAuthorizationAmountByTransactionId(OrderModel orderModel, String transactionId) {
        List<PaymentTransactionEntryModel> multiCaptureableTransactions = getMultiCaptureableTransactions(orderModel);
        PaymentTransactionEntryModel authorizeTransaction = null;
        for (PaymentTransactionEntryModel transaction : multiCaptureableTransactions) {
            if (transaction.getRequestId().equals(transactionId)) {
                authorizeTransaction = transaction;
            }
        }
        return Optional.ofNullable(authorizeTransaction).map(PaymentTransactionEntryModel::getAmount)
                .orElse(BigDecimal.ZERO);
    }

    @Override
    public boolean isSubmitForSettlementAvailable(OrderModel orderModel) {
        return braintreeSubmitForSettlementService.isSubmitForSettlementAvailable(orderModel);
    }

    @Override
    public Predicate<PaymentTransactionEntryModel> getCaptureTransactionTypePredicate() {
        return braintreePartialCaptureService.getCaptureTransactionTypePredicate();
    }

    @Override
    public Predicate<PaymentTransactionEntryModel> getAuthorizeTransactionTypePredicate() {
        return braintreePartialCaptureService.getAuthorizeTransactionTypePredicate();
    }

    @Override
    public Predicate<PaymentTransactionEntryModel> getRefundTransactionTypePredicate() {
        return braintreePartialCaptureService.getRefundTransactionTypePredicate();
    }

    public void setBraintreePartialCaptureService(BraintreePartialCaptureService braintreePartialCaptureService) {
        this.braintreePartialCaptureService = braintreePartialCaptureService;
    }

    public void setBraintreeSubmitForSettlementService(BraintreeSubmitForSettlementService braintreeSubmitForSettlementService) {
        this.braintreeSubmitForSettlementService = braintreeSubmitForSettlementService;
    }

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

    public void setBrainTreePaymentTransactionService(BrainTreePaymentTransactionService brainTreePaymentTransactionService) {
        this.brainTreePaymentTransactionService = brainTreePaymentTransactionService;
    }
}
