package com.braintree.customersupportbackoffice.widgets.order.multiplecapture;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customersupportbackoffice.widgets.order.AbstractOrderActionController;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.facade.backoffice.BraintreeBackofficeMultiCaptureFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import com.hybris.cockpitng.annotations.SocketEvent;
import com.hybris.cockpitng.annotations.ViewEvent;
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.model.PaymentTransactionEntryModel;
import de.hybris.platform.servicelayer.session.SessionExecutionBody;
import de.hybris.platform.servicelayer.session.SessionService;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Textbox;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.MULTI_CAPTURE_TEXT_1;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.MULTI_CAPTURE_TEXT_2;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.MULTI_CAPTURE_TEXT_3;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.MULTI_CAPTURE_TITLE;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.PARTIAL_CAPTURE_AMOUNT_NOT_MATH_ERROR;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.PARTIAL_CAPTURE_ERROR;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.PARTIAL_CAPTURE_SUCCESS;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.PARTIAL_CAPTURE_TITLE;

public class BrainTreeMultipleCaptureController extends AbstractOrderActionController {

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

    private static final String IN_SOCKET = "inputObject";
    private static final String OUT_MODIFIED_ITEM = "modifiedItem";
    private static final String ON_OK_EVENT = "onOK";
    private static final String CURRENT_SITE = "currentSite";

    private OrderModel order;
    private String transactionId;

    @Wire
    private Textbox orderCode;
    @Wire
    private Textbox customer;
    @Wire
    private Textbox amount;
    @Wire
    private Combobox transactions;
    @Wire
    private Checkbox forceCompleteOrder;

    @Resource
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;

    @Resource
    private BrainTreeConfigService brainTreeConfigService;

    @Resource
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;

    @Resource
    private BraintreeBackofficeMultiCaptureFacade braintreeBackofficeMultiCaptureFacade;

    @Resource
    private SessionService sessionService;

    @SocketEvent(socketId = IN_SOCKET)
    public void initCreateReturnRequestForm(OrderModel inputOrder) {
        sessionService.executeInLocalViewWithParams(Map.of(CURRENT_SITE, inputOrder.getSite()), new SessionExecutionBody() {
            @Override
            public Object execute() {
                setOrder(inputOrder);
                getWidgetInstanceManager().setTitle(
                        getWidgetInstanceManager().getLabel("braintreecustomersupportbackoffice.multiplecapture.confirm.title")
                                + " " + getOrder().getCode());
                orderCode.setValue(getOrder().getCode());
                customer.setValue(getOrder().getUser().getDisplayName());
                configureTransactionsCombobox(inputOrder);
                transactions.setSelectedIndex(0);
                amount.setValue(getAmount());
                transactions.addEventListener("onChange", event -> amount.setValue(getAmount()));
                return null;
            }
        });
    }

    private String getAmount() {
        PaymentTransactionEntryModel currentModel = (PaymentTransactionEntryModel) transactions.getSelectedItem()
                .getValue();
        return formatAmount(braintreeBackofficeMultiCaptureFacade.getTransactionAmount(currentModel, this.getOrder()));
    }

    private void configureTransactionsCombobox(final OrderModel inputOrder) {
        List<PaymentTransactionEntryModel> trans = braintreeBackofficeMultiCaptureFacade
                .getMultiCaptureableTransactions(inputOrder);
        LOG.info("MultiCaptureable transactions: " + trans);

        for (PaymentTransactionEntryModel v : trans) {
            Comboitem ci = createComboItem(v);
            if (!brainTreeConfigService.isStoreInVault()
                    && !brainTreeCheckoutFacade.isPayPalCheckout(order)) {
                amount.setValue(v.getAmount().toString());
                amount.setDisabled(true);
            }
            transactions.appendChild(ci);
        }

        if (!transactions.getItems().isEmpty()) {
            transactions.setSelectedItem(transactions.getItems().get(0));
        }
    }

    @ViewEvent(componentID = "multiplecapturerequest", eventName = Events.ON_CLICK)
    public void confirm() {
        if (transactions.getSelectedIndex() != -1) {
            transactionId = transactions.getSelectedItem().getLabel();
        }
        if (!validateAmount()) {
            showMsgWithEvent(
                    getLabel(MULTI_CAPTURE_TEXT_1) + System.lineSeparator() + System.lineSeparator()
                            + getLabel(MULTI_CAPTURE_TEXT_2) + System.lineSeparator() + System.lineSeparator()
                            + getLabel(MULTI_CAPTURE_TEXT_3), getLabel(MULTI_CAPTURE_TITLE),
                    Messagebox.OK | Messagebox.CANCEL, Messagebox.ERROR, event -> {
                        if (event.getName().equalsIgnoreCase(ON_OK_EVENT)) {
                            captureInLocalView();
                        }
                    });
            sendOutput(OUT_MODIFIED_ITEM, order);
        } else {
            captureInLocalView();
        }
    }

    protected Comboitem createComboItem(PaymentTransactionEntryModel transaction) {
        Comboitem ci = new Comboitem();
        ci.setValue(transaction);
        ci.setLabel(transaction.getRequestId());
        return ci;
    }

    protected void showMsg(String message, String title, int buttons, String icon) {
        Messagebox.show(message, title, buttons, icon);
    }

    protected void showMsgWithEvent(String message, String title, int buttons, String icon, EventListener<Event> listener) {
        Messagebox.show(message, title, buttons, icon, listener);
    }

    private void captureInLocalView() {
        sessionService.executeInLocalViewWithParams(Map.of(CURRENT_SITE, order.getSite()), new SessionExecutionBody() {
            @Override
            public void executeWithoutResult() {
                capture();
            }
        });
    }

    private void capture() {
        try {
            if (order != null && brainTreePaymentTransactionService.isValidTransactionId(order, transactionId)) {
                PaymentTransactionEntryData orderCaptureResult = processCapture();
                if (TransactionStatus.ACCEPTED.name().equals(orderCaptureResult.getTransactionStatus())) {
                    showMsg(getLabel(PARTIAL_CAPTURE_SUCCESS), getLabel(PARTIAL_CAPTURE_TITLE), Messagebox.OK,
                            Messagebox.INFORMATION);
                } else {
                    showMsg(getLabel(PARTIAL_CAPTURE_ERROR) + ". " + orderCaptureResult.getTransactionStatusDetails(),
                            getLabel(PARTIAL_CAPTURE_TITLE), Messagebox.OK, Messagebox.ERROR);
                }

            } else {
                showMsg(getLabel(PARTIAL_CAPTURE_AMOUNT_NOT_MATH_ERROR) + " " + formatAmount(
                                getAmountAvailableForMultiCapture()),
                        getLabel(PARTIAL_CAPTURE_TITLE), Messagebox.OK, Messagebox.ERROR);
            }
        } catch (BraintreeErrorException e) {
            showMsg(e.getMessage(), getLabel(PARTIAL_CAPTURE_ERROR), Messagebox.OK, Messagebox.ERROR);
        }
        sendOutput(OUT_MODIFIED_ITEM, order);
    }

    private PaymentTransactionEntryData processCapture() throws BraintreeErrorException {
        final BigDecimal currentAmount = new BigDecimal(this.amount.getValue());
        final PaymentTransactionEntryModel entry = this.transactions.getSelectedItem().getValue();
        final boolean isOrderToBeForceCompleted = forceCompleteOrder.isChecked();
        if (braintreeBackofficeMultiCaptureFacade.isPartialCapturePossible(order)
                && brainTreeCheckoutFacade.isPayPalCheckout(order) && !isCaptureAmountEqualsTotal(entry, currentAmount)) {
            return braintreeBackofficeMultiCaptureFacade.partialCapture(order, currentAmount, entry, isOrderToBeForceCompleted);
        } else {
            if (braintreeBackofficeMultiCaptureFacade.isSubmitForSettlementAvailable(order)) {
                return braintreeBackofficeMultiCaptureFacade.submitForSettlement(order, currentAmount, entry, isOrderToBeForceCompleted);
            }
            if (brainTreePaymentTransactionService.canPerformDelayedCapture(entry, currentAmount)) {
                return brainTreeCheckoutFacade.capturePayment(order, currentAmount, entry, isOrderToBeForceCompleted);
            }

        }
        throw new BraintreeErrorException("Error during order capture process");
    }

    private boolean isCaptureAmountEqualsTotal(final PaymentTransactionEntryModel entry, final BigDecimal selectedAmount) {
        return entry.getAmount().compareTo(selectedAmount) < 1;
    }

    private BigDecimal getAmountAvailableForMultiCapture() {
        if (order != null) {
            return braintreeBackofficeMultiCaptureFacade.getPossibleAmountForCapture(order);
        }
        return BigDecimal.ZERO;
    }

    private boolean validateAmount() {
        String value = amount.getValue();
        if (StringUtils.isBlank(value)) {
            throw new WrongValueException(amount, getLabel("bt.customersupport.order.amount.error.empty"));
        }
        try {
            BigDecimal bigDecimalValue = new BigDecimal(value);
            if (BigDecimal.ZERO.equals(bigDecimalValue)) {
                throw new WrongValueException(amount, getLabel("bt.customersupport.order.amount.error.zero"));
            } else if (braintreeBackofficeMultiCaptureFacade.getAuthorizationAmountByTransactionId(order, transactionId)
                    .compareTo(bigDecimalValue) < 0) {
                return false;
            }
        } catch (NumberFormatException e) {
            throw new WrongValueException(amount, getLabel("bt.customersupport.order.amount.error.number.format"));
        }
        return true;
    }

    public OrderModel getOrder() {
        return order;
    }

    public void setOrder(OrderModel order) {
        this.order = order;
    }
}
