package com.braintree.order.capture.partial.services.impl;

import com.braintree.command.request.BrainTreeSaleTransactionRequest;
import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.order.capture.partial.services.BraintreePartialCaptureService;
import com.braintree.order.capture.partial.strategy.BraintreePartialOrderCalculationStrategy;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.google.common.base.Preconditions;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.dto.CardInfo;
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.model.ModelService;
import org.apache.log4j.Logger;

import java.math.BigDecimal;
import java.util.function.Predicate;

import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNullStandardMessage;

public class DefaultBraintreePartialCaptureService implements BraintreePartialCaptureService {

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

    private static final String ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE = "orderModel can not be null";
    private BraintreePartialOrderCalculationStrategy partialOrderCalculationStrategy;
    private BrainTreeConfigService brainTreeConfigService;
    private BrainTreePaymentService brainTreePaymentService;
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;
    private BrainTreeTransactionService brainTreeTransactionService;
    private ModelService modelService;


    @Override
    public PaymentTransactionEntryModel partialCapture(OrderModel orderModel, BigDecimal captureAmount,
                                                       final PaymentTransactionEntryModel entry, boolean isOrderToBeForceCompleted) {
        validateParameterNotNull(orderModel, ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE);

        BrainTreeSaleTransactionRequest request = createRequest(orderModel,
                captureAmount, entry.getRequestId());
        BrainTreeSaleTransactionResult brainTreeSaleTransactionResult = brainTreePaymentService
                .partialCaptureTransaction(request);

        PaymentTransactionEntryModel transactionEntry = brainTreeTransactionService.createPaymentTransaction(entry.getPaymentTransaction(),
                brainTreeSaleTransactionResult, captureAmount, PaymentTransactionType.PARTIAL_CAPTURE, entry.getRequestId());

        if (brainTreeSaleTransactionResult.isSuccess()) {
            if (brainTreePaymentTransactionService.isOrderFullyCaptured(entry) || isOrderToBeForceCompleted) {
                brainTreePaymentTransactionService.processPaymentCapturedOrder(orderModel);
            } else {
                brainTreePaymentTransactionService.setOrderStatus(orderModel, OrderStatus.PARTIAL_CAPTURE);
            }
            brainTreeTransactionService.saveBraintreeTransactionStatus(
                    brainTreeSaleTransactionResult.getStatus(), transactionEntry.getPaymentTransaction());
        }

        return transactionEntry;

    }

    private BrainTreeSaleTransactionRequest createRequest(OrderModel orderModel, BigDecimal captureAmount,
        String authorizeTransactionID) {
        CustomerModel customer = (CustomerModel) orderModel.getUser();

        BrainTreeSaleTransactionRequest request = new BrainTreeSaleTransactionRequest(customer.getUid(),
            createCard(orderModel.getPaymentAddress()), null, captureAmount,
            createBillingInfo(orderModel.getDeliveryAddress()));

        final BrainTreePaymentInfoModel brainTreePaymentInfoModel = (BrainTreePaymentInfoModel) orderModel
            .getPaymentInfo();

        request.setUsePaymentMethodToken(Boolean.TRUE);
        request.setPaymentMethodToken(brainTreePaymentInfoModel.getPaymentMethodToken());
        request.setRequestId(authorizeTransactionID);
        return request;
    }

    private BillingInfo createBillingInfo(final AddressModel addressModel) {
        final BillingInfo shippingInfo = new BillingInfo();
        shippingInfo.setFirstName(addressModel.getFirstname());
        shippingInfo.setLastName(addressModel.getLastname());
        shippingInfo.setEmail(addressModel.getEmail());
        shippingInfo.setPostalCode(addressModel.getPostalcode());
        shippingInfo.setStreet1(addressModel.getLine1());
        shippingInfo.setStreet2(addressModel.getLine2());
        return shippingInfo;
    }

    private CardInfo createCard(final AddressModel addressModel) {
        final CardInfo cardInfo = new CardInfo();
        cardInfo.setBillingInfo(createBillingInfo(addressModel));
        return cardInfo;
    }

    @Override
    public BigDecimal getPossibleAmountForCapture(OrderModel orderModel) {
        validateParameterNotNull(orderModel, ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE);
        modelService.refresh(orderModel);
        return partialOrderCalculationStrategy.calculateCaptureAmount(orderModel);
    }

    @Override
    public BigDecimal getPossibleAmountForCaptureForAuthorizedTransaction
        (PaymentTransactionModel paymentTransactionModel, BigDecimal totalAmount) {
        return partialOrderCalculationStrategy.calculateTransaction(paymentTransactionModel, totalAmount.doubleValue());
    }

    @Override
    public boolean isPartialCapturePossible(OrderModel orderModel) {
        validateParameterNotNull(orderModel, ORDER_MODEL_CAN_NOT_BE_NULL_MESSAGE);
        boolean result = brainTreeConfigService.isMultiCaptureEnabled();
        LOG.warn("isPartialCapturePossible, result: " + result);
        return result;
    }

    public BigDecimal getCapturedAmountForAuthorizedTransaction(final OrderModel orderModel,
        final String transactionID) {
        validateParameterNotNullStandardMessage("transactionID", transactionID);

        return orderModel.getPaymentTransactions().stream()
            .flatMap(paymentTransaction -> paymentTransaction.getEntries().stream())
            .filter(entry -> transactionID.equals(entry.getSubscriptionID()))
            .filter(getCaptureTransactionTypePredicate())
            .map(PaymentTransactionEntryModel::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public BigDecimal getTransactionAmount(final PaymentTransactionEntryModel entry,
                                           final OrderModel order) {
        validateParameterNotNullStandardMessage("entry", entry);
        validateParameterNotNullStandardMessage("orderModel", order);

        BigDecimal possibleAmountForCapture = getPossibleAmountForCaptureForAuthorizedTransaction(
                entry.getPaymentTransaction(), entry.getAmount());
        BigDecimal amountCapturedForTransaction = getCapturedAmountForAuthorizedTransaction(order,
                entry.getRequestId());
        BigDecimal currentPaymentEntryAmount = entry.getAmount();
        BigDecimal notCaptured = currentPaymentEntryAmount.subtract(amountCapturedForTransaction);

        if (notCaptured.compareTo(BigDecimal.ZERO) > 0) {
            return possibleAmountForCapture.compareTo(notCaptured) > 0 ? notCaptured : possibleAmountForCapture;
        }

        return BigDecimal.ZERO;
    }

    public Predicate<PaymentTransactionEntryModel> getCaptureTransactionTypePredicate() {
        return transaction -> (PaymentTransactionType.CAPTURE.equals(transaction.getType())
            || PaymentTransactionType.PARTIAL_CAPTURE.equals(transaction.getType()))
            && TransactionStatus.ACCEPTED.toString().equals(transaction.getTransactionStatus());
    }

    public Predicate<PaymentTransactionEntryModel> getAuthorizeTransactionTypePredicate() {
        return transaction -> PaymentTransactionType.AUTHORIZATION.equals(transaction.getType())
            && TransactionStatus.ACCEPTED.toString().equals(transaction.getTransactionStatus());
    }

    public Predicate<PaymentTransactionEntryModel> getRefundTransactionTypePredicate() {
        return transaction -> PaymentTransactionType.REFUND_PARTIAL.equals(transaction.getType())
            && TransactionStatus.ACCEPTED.toString().equals(transaction.getTransactionStatus());
    }

    protected void validateParameterNotNull(Object parameter, String nullMessage) {
        Preconditions.checkArgument(parameter != null, nullMessage);
    }

    public void setPartialOrderCalculationStrategy(
        BraintreePartialOrderCalculationStrategy partialOrderCalculationStrategy) {
        this.partialOrderCalculationStrategy = partialOrderCalculationStrategy;
    }

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

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

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

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

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

}
