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

import com.braintree.command.result.BrainTreeAdjustedAuthorizationResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customersupportbackoffice.widgets.order.AbstractOrderActionController;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.facade.backoffice.BraintreeBackofficeAdjustAuthorizationFacade;
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.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.springframework.beans.factory.annotation.Autowired;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.select.annotation.Wire;
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.math.RoundingMode;
import java.util.List;
import java.util.Map;

import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.ADJUST_AUTHORIZATION_ERROR;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.ADJUST_AUTHORIZATION_SUCCESS;
import static com.braintree.customersupportbackoffice.constants.BraintreecustomersupportbackofficeConstants.OrderManagementActions.ADJUST_AUTHORIZATION_TITLE;

public class BrainTreeAdjustAuthorizationController extends AbstractOrderActionController {

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

    private static final String IN_SOCKET = "inputObject";
    private static final String OUT_MODIFIED_ITEM = "modifiedItem";
    private static final String CURRENT_SITE = "currentSite";
    private static final String ADJUSTAUTHORIZATION_CONFIRM_TITLE = "braintreecustomersupportbackoffice.adjustauthorization.confirm.title";
    private static final String ON_CHANGE = "onChange";
    private static final String ORDER_AMOUNT_ERROR_EMPTY = "bt.customersupport.order.adjust.authorization.amount.error.empty";
    private static final String ORDER_AMOUNT_ERROR_ZERO = "bt.customersupport.order.adjust.authorization.amount.error.zero";
    private static final String ORDER_AMOUNT_ERROR_NUMBER_FORMAT = "bt.customersupport.order.adjust.authorization.amount.error.number.format";
    private static final String ORDER_ADJUST_AUTHORIZATION_AMOUNT_ERROR_EQUAL = "bt.customersupport.order.adjust.authorization.amount.error.equal";
    private static final String ORDER_ADJUST_AUTHORIZATION_ERROR_TITLE = "bt.customersupport.widget.order.adjust.authorization.error.title";
    private static final String ADJUST_AUTHORIZATION_INVALID_TRANSACTION_ID_ERROR = "bt.customersupport.widget.order.adjust.authorization.invalid.transaction.id.error";
    private static final String AUTHORIZED_TRANSACTIONS_LOG_MESSAGE = "Authorized transactions: %s";
    private static final String ERROR_CODE_LABEL = "Error code ";

    private OrderModel order;
    private String transactionId;

    @Wire
    private Textbox orderCode;
    @Wire
    private Textbox customer;
    @Wire
    private Textbox amount;
    @Wire
    private Textbox originalAuthorizationAmount;
    @Wire
    private Combobox transactions;

    private PaymentTransactionEntryModel transaction;

    @Resource
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;

    @Resource
    private BrainTreeConfigService brainTreeConfigService;

    @Resource
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;

    @Autowired
    private BraintreeBackofficeAdjustAuthorizationFacade braintreeBackofficeAdjustAuthorizationFacade;

    @Autowired
    private BraintreeBackofficeMultiCaptureFacade braintreeBackofficeMultiCaptureFacade;

    @Autowired
    private SessionService sessionService;

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

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

    private String getOriginalAuthorizationAmount() {
        String formattedAmount = StringUtils.EMPTY;
        BigDecimal authorizationAmountByTransactionId =
                braintreeBackofficeAdjustAuthorizationFacade.getOriginalAmountByTransactionId(order, transactions.getSelectedItem().getLabel());
        if (authorizationAmountByTransactionId != null) {
            formattedAmount = formatAmount(authorizationAmountByTransactionId);
        }
        return formattedAmount;
    }

    private void configureTransactionsCombobox(final OrderModel inputOrder) {
        List<PaymentTransactionEntryModel> trans = braintreeBackofficeAdjustAuthorizationFacade
                .getAuthorizedTransactions(inputOrder);
        LOG.info(String.format(AUTHORIZED_TRANSACTIONS_LOG_MESSAGE, trans));

        for (PaymentTransactionEntryModel v : trans) {
            Comboitem ci = createComboItem(v);
            amount.setValue(v.getAmount().toString());
            transactions.appendChild(ci);
        }

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

    @ViewEvent(componentID = "adjustauthorizationrequest", eventName = Events.ON_CLICK)
    public void confirm() {
        sessionService.executeInLocalViewWithParams(Map.of(CURRENT_SITE, order.getSite()), new SessionExecutionBody() {
            @Override
            public Object execute() {
                if (transactions.getSelectedIndex() != -1) {
                    transactionId = transactions.getSelectedItem().getLabel();
                }
                validateAmount();
                adjustAuthorization();
                return null;
            }
        });
    }

    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);
    }

    private void adjustAuthorization() {
        try {
            if (order != null && brainTreePaymentTransactionService.isValidTransactionId(order, transactionId)) {
                BrainTreeAdjustedAuthorizationResult adjustAuthorizationResult =
                        braintreeBackofficeAdjustAuthorizationFacade.adjustAuthorization(order, transactionId, roundNumberToTwoDecimalPlaces(amount.getValue()));

                if (adjustAuthorizationResult.isSuccess()) {
                    showMsg(getLabel(ADJUST_AUTHORIZATION_SUCCESS), getLabel(ADJUST_AUTHORIZATION_TITLE), Messagebox.OK,
                            Messagebox.INFORMATION);
                } else {
                    showMsg(ERROR_CODE_LABEL + adjustAuthorizationResult.getErrorCode() +
                                            ": " + adjustAuthorizationResult.getErrorMessage(),
                                    getLabel(ORDER_ADJUST_AUTHORIZATION_ERROR_TITLE), Messagebox.OK, Messagebox.ERROR);
                }
            } else {
                showMsg(getLabel(ADJUST_AUTHORIZATION_INVALID_TRANSACTION_ID_ERROR),
                        getLabel(ORDER_ADJUST_AUTHORIZATION_ERROR_TITLE), Messagebox.OK, Messagebox.ERROR);
            }
        } catch (BraintreeErrorException e) {
            LOG.error(String.format("Order authorization was not adjusted : %s", e.getMessage()), e);
            showMsg(e.getMessage(), getLabel(ADJUST_AUTHORIZATION_ERROR), Messagebox.OK, Messagebox.ERROR);
        }
        sendOutput(OUT_MODIFIED_ITEM, order);
    }

    private boolean validateAmount() {
        String value = amount.getValue();
        if (StringUtils.isBlank(value)) {
            throw new WrongValueException(amount, getLabel(ORDER_AMOUNT_ERROR_EMPTY));
        }
        try {
            BigDecimal bigDecimalValue = new BigDecimal(value);
            if (bigDecimalValue.compareTo(BigDecimal.ZERO) < 1) {
                throw new WrongValueException(amount, getLabel(ORDER_AMOUNT_ERROR_ZERO));
            } else if (isAdjustedAmountTheSame(bigDecimalValue)) {
                throw new WrongValueException(amount, getLabel(ORDER_ADJUST_AUTHORIZATION_AMOUNT_ERROR_EQUAL));
            }
        } catch (NumberFormatException e) {
            throw new WrongValueException(amount, getLabel(ORDER_AMOUNT_ERROR_NUMBER_FORMAT));
        }
        return true;
    }

    private boolean isAdjustedAmountTheSame(BigDecimal bigDecimalValue) {
        BigDecimal currentAuthorizationAmount = roundNumberToTwoDecimalPlaces(braintreeBackofficeAdjustAuthorizationFacade.getAmountByTransactionId(order, transactionId));
        BigDecimal adjustedAuthorizationAmount = roundNumberToTwoDecimalPlaces(bigDecimalValue);
        return currentAuthorizationAmount.equals(adjustedAuthorizationAmount);
    }

    private BigDecimal roundNumberToTwoDecimalPlaces(final String number) {
        return new BigDecimal(number).setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal roundNumberToTwoDecimalPlaces(final BigDecimal bigDecimal) {
        return bigDecimal.setScale(2, RoundingMode.HALF_UP);
    }

    public OrderModel getOrder() {
        return order;
    }

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

}
