package com.braintree.transaction.service.impl;

import com.braintree.constants.BraintreeConstants;
import com.braintree.constants.BraintreeConstants.PropertyConfiguration;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.model.BrainTreeTransactionDetailModel;
import com.braintree.order.capture.partial.process.BraintreeOrderCompleteProcessService;
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.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.BusinessProcessService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;

import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNull;
import static de.hybris.platform.util.localization.Localization.getLocalizedString;

public class DefaultBrainTreePaymentTransactionService implements BrainTreePaymentTransactionService {

    private static final String ORDER_PROCESS_PREFIX = "order-process";
    private static final String ORDER_PROCESS_SUFFIX = "_SubmitForSettlementEvent";
    private static final String ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE = "orderModel can not be null";

    private FlexibleSearchService flexibleSearchService;
    private BusinessProcessService businessProcessService;
    private BraintreeOrderCompleteProcessService orderCompleteProcessService;
    private ModelService modelService;

    @Override
    public List<PaymentTransactionModel> getTransactionsByRequestIdAndPaymentProvider(String requestId,
                                                                                      String paymentProvider) {
        final PaymentTransactionModel transactionModel = new PaymentTransactionModel();

        transactionModel.setRequestId(requestId);
        transactionModel.setPaymentProvider(paymentProvider);

        return flexibleSearchService.getModelsByExample(transactionModel);
    }

    @Override
    public PaymentTransactionModel getTransactionByRequestIdAndPaymentProvider(final String requestId,
                                                                               final String paymentProvider) {
        final List<PaymentTransactionModel> paymentTransactionModels = getTransactionsByRequestIdAndPaymentProvider(
                requestId,
                paymentProvider);
        return paymentTransactionModels == null || paymentTransactionModels.isEmpty() ? null
                : paymentTransactionModels.get(0);
    }

    @Override
    public List<PaymentTransactionEntryModel> getTransactionEntriesByRequestId(String requestId) {
        final PaymentTransactionEntryModel transactionEntryModel = new PaymentTransactionEntryModel();

        transactionEntryModel.setRequestId(requestId);

        return flexibleSearchService.getModelsByExample(transactionEntryModel);
    }

    @Override
    public PaymentTransactionEntryModel getTransactionEntryByRequestId(String requestId) {
        final List<PaymentTransactionEntryModel> transactionEntryModels = getTransactionEntriesByRequestId(
                requestId);
        return transactionEntryModels == null || transactionEntryModels.isEmpty() ? null
                : transactionEntryModels.get(0);
    }

    @Override
    public void continueSubmitOrder(BrainTreeTransactionDetailModel currentTransaction, BigDecimal amount) {
        PaymentTransactionModel paymentTransactionModel = getTransactionByRequestIdAndPaymentProvider(
                currentTransaction.getId(),
                BraintreeConstants.BRAINTREE_PROVIDER_NAME);

        if (paymentTransactionModel != null) {
            resumeOrderProcess(paymentTransactionModel, currentTransaction, amount);
        }
    }

    @Override
    public void continueOrderProcess(OrderModel orderModel) {
        orderCompleteProcessService.startOrderCompletionProcess(orderModel);
    }

    @Override
    public boolean isValidTransactionId(final OrderModel orderModel, final String transactionId) {
        validateParameterNotNull(orderModel, ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE);
        boolean result = false;

        for (final PaymentTransactionModel paymentTransactionModel : orderModel.getPaymentTransactions()) {
            for (final PaymentTransactionEntryModel paymentTransactionEntryModel : paymentTransactionModel
                    .getEntries()) {
                if ((paymentTransactionEntryModel.getType() == PaymentTransactionType.AUTHORIZATION
                        && TransactionStatus.ACCEPTED.name().equals(paymentTransactionEntryModel.getTransactionStatus()))
                        && paymentTransactionEntryModel.getRequestId().equals(transactionId)) {
                    result = true;
                }
            }
        }
        return result;
    }

    @Override
    public boolean isOrderFullyCaptured(final PaymentTransactionEntryModel authorizationTransactionEntry) {
        BigDecimal authorizedAmount = authorizationTransactionEntry.getAmount();
        BigDecimal capturedAmount = getTotalCapturedAmountForTransaction(authorizationTransactionEntry.getPaymentTransaction());
        return capturedAmount.compareTo(authorizedAmount) >= 0;
    }

    @Override
    public boolean canPerformDelayedCapture(final PaymentTransactionEntryModel entry, final BigDecimal selectedAmount)
            throws BraintreeErrorException {
        BigDecimal authorizedAmount = entry.getAmount();
        BigDecimal capturedAmount = getTotalCapturedAmountForTransaction(entry.getPaymentTransaction());

        if (capturedAmount.add(selectedAmount).compareTo(authorizedAmount) > 0) {
            throw new BraintreeErrorException(getLocalizedString(PropertyConfiguration.EXCEEDED_CAPTURE_AMOUNT));
        }
        return true;
    }

    private BigDecimal getTotalCapturedAmountForTransaction(PaymentTransactionModel transaction) {
        return transaction.getEntries().stream()
                .filter(this::isSuccessfulCaptureTransactionEntry)
                .map(PaymentTransactionEntryModel::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private boolean isSuccessfulCaptureTransactionEntry(PaymentTransactionEntryModel entry) {
        return (entry.getType().equals(PaymentTransactionType.CAPTURE)
                || entry.getType().equals(PaymentTransactionType.PARTIAL_CAPTURE))
                && TransactionStatus.ACCEPTED.toString().equals(entry.getTransactionStatus());
    }

    @Override
    public void setOrderStatus(final OrderModel orderModel, final OrderStatus orderStatus) {
        orderModel.setStatus(orderStatus);
        modelService.save(orderModel);
        modelService.refresh(orderModel);
    }

    @Override
    public void processPaymentCapturedOrder(OrderModel orderModel) {
        setOrderStatus(orderModel, OrderStatus.PAYMENT_CAPTURED);
        continueOrderProcess(orderModel);
    }

    private void resumeOrderProcess(PaymentTransactionModel paymentTransactionModel,
                                    BrainTreeTransactionDetailModel currentTransaction, BigDecimal amount) {
        OrderModel order = (OrderModel) paymentTransactionModel.getOrder();
        setAmount(paymentTransactionModel, amount);

        OrderProcessModel orderProcess = getOrderProcess(order);

        if (orderProcess != null) {
            businessProcessService.triggerEvent(orderProcess.getCode() + ORDER_PROCESS_SUFFIX);
        }

    }

    @Override
    public void resumeOrderProcess(OrderModel order) {
        if (order != null) {
            OrderProcessModel orderProcess = getOrderProcess(order);

            if (orderProcess != null) {
                businessProcessService.triggerEvent(orderProcess.getCode() + ORDER_PROCESS_SUFFIX);
            }
        }
    }

    private OrderProcessModel getOrderProcess(OrderModel order) {
        Collection<OrderProcessModel> orderProcess = order.getOrderProcess();
        for (OrderProcessModel processModel : orderProcess) {
            if (ORDER_PROCESS_PREFIX.equals(processModel.getProcessDefinitionName())) {
                return processModel;
            }
        }
        return null;
    }

    private void setAmount(PaymentTransactionModel paymentTransactionModel, BigDecimal amount) {
        for (PaymentTransactionEntryModel entry : paymentTransactionModel.getEntries()) {
            if (PaymentTransactionType.AUTHORIZATION.equals(entry.getType())) {
                entry.setSubmittedForSettlementAmount(amount);
                modelService.save(entry);
            }
        }
    }

    public void setFlexibleSearchService(final FlexibleSearchService flexibleSearchService) {
        this.flexibleSearchService = flexibleSearchService;
    }

    public void setBusinessProcessService(BusinessProcessService businessProcessService) {
        this.businessProcessService = businessProcessService;
    }

    public void setOrderCompleteProcessService(
            BraintreeOrderCompleteProcessService orderCompleteProcessService) {
        this.orderCompleteProcessService = orderCompleteProcessService;
    }

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

}
