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

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.facade.backoffice.BraintreeBackofficeMultiCaptureFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import com.hybris.cockpitng.engine.WidgetInstanceManager;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.user.UserModel;
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.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
import org.zkoss.zk.ui.WrongValueException;
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 java.math.BigDecimal;
import java.util.List;

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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyMap;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class BrainTreeMultipleCaptureControllerTest {
    private static final String MULTICAPTURE_CONFIRM_TITLE = "braintreecustomersupportbackoffice.multiplecapture.confirm.title";
    private static final String MULTICAPTURE_CONFIRM_TITLE_LABEL = "MULTICAPTURE_CONFIRM_TITLE_LABEL";
    private static final String TEST_ORDER_CODE = "TEST_ORDER_CODE";
    private static final String TEST_DISPLAY_NAME = "TEST_DISPLAY_NAME";
    private static final String TEST_COMBOITEM_LABEL = "TEST_COMBOITEM_LABEL";
    private static final String ORDER_AMOUNT_ERROR_EMPTY = "bt.customersupport.order.amount.error.empty";
    private static final String ORDER_AMOUNT_ERROR_NUMBER_FORMAT = "bt.customersupport.order.amount.error.number.format";
    private static final String ORDER_AMOUNT_ERROR_ZERO = "bt.customersupport.order.amount.error.zero";
    private static final String ORDER_AMOUNT_ERROR_ZERO_LABEL = "ORDER_AMOUNT_ERROR_ZERO_LABEL";
    private static final String ORDER_AMOUNT_ERROR_NUMBER_FORMAT_LABEL = "ORDER_AMOUNT_ERROR_NUMBER_FORMAT_LABEL";
    private static final String ORDER_AMOUNT_ERROR_EMPTY_LABEL = "ORDER_AMOUNT_ERROR_EMPTY_LABEL";
    private static final String PARTIAL_CAPTURE_SUCCESS_LABEL = "PARTIAL_CAPTURE_SUCCESS_LABEL";
    private static final String PARTIAL_CAPTURE_TITLE_LABEL = "PARTIAL_CAPTURE_TITLE_LABEL";
    private static final String PARTIAL_CAPTURE_ERROR_LABEL = "PARTIAL_CAPTURE_ERROR_LABEL";
    private static final String TRANSACTION_STATUS_LABEL = "TRANSACTION_STATUS_LABEL";
    private static final String TEST_PARTIAL_CAPTURE_AMOUNT = "7";
    private static final String OUT_MODIFIED_ITEM = "modifiedItem";
    private static final double TEST_TOTAL_ORDER_AMOUNT_GREATHER_THEN_CAPTURE_AMOUNT = 10;
    private final int ZERO_INDEX = 0;
    @Mock
    private SessionService sessionService;
    @Mock
    private WidgetInstanceManager widgetInstanceManager;
    @Mock
    private Comboitem comboitem;
    @Mock
    private Combobox transactions;
    @Mock
    private Checkbox checkbox;
    @Mock
    private Textbox orderCode;
    @Mock
    private Textbox amount;
    @Spy
    private OrderModel mockedOrder;
    @Spy
    private UserModel mockedUser;
    @Mock
    private Textbox customer;
    @Mock
    private BaseSiteModel baseSiteModel;
    @Mock
    private OrderModel order;
    @Mock
    private PaymentTransactionEntryData paymentTransactionEntryData;
    @Mock
    private PaymentTransactionEntryModel paymentTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel selectedTransactionEntry;
    @Mock
    private BraintreeBackofficeMultiCaptureFacade braintreeBackofficeMultiCaptureFacade;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;
    @Mock
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Mock
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;
    @Spy
    @InjectMocks
    private BrainTreeMultipleCaptureController unit;

    @Test
    public void shouldInitCreateReturnRequestFormWhenOrderHaveMultiCaptureableTransactions() {
        final Object[] executionBodyResult = new Object[1];
        when(widgetInstanceManager.getLabel(MULTICAPTURE_CONFIRM_TITLE)).thenReturn(MULTICAPTURE_CONFIRM_TITLE_LABEL);
        when(mockedOrder.getCode()).thenReturn(TEST_ORDER_CODE);
        when(mockedOrder.getUser()).thenReturn(mockedUser);
        when(mockedOrder.getSite()).thenReturn(baseSiteModel);
        when(mockedUser.getDisplayName()).thenReturn(TEST_DISPLAY_NAME);
        String TEST_TRANSACTION_AMOUNT = "5";
        when(braintreeBackofficeMultiCaptureFacade.getTransactionAmount(any(), any())).thenReturn(new BigDecimal(TEST_TRANSACTION_AMOUNT));
        when(braintreeBackofficeMultiCaptureFacade.getMultiCaptureableTransactions(any())).thenReturn(List.of(paymentTransactionEntryModel));
        String TEST_ORDER_TRANSACTION_AMOUNT = "6";
        when(transactions.getSelectedItem()).thenReturn(comboitem);
        when(paymentTransactionEntryModel.getAmount()).thenReturn(new BigDecimal(TEST_ORDER_TRANSACTION_AMOUNT));
        when(brainTreeConfigService.isStoreInVault()).thenReturn(false);
        when(brainTreeCheckoutFacade.isPayPalCheckout(any())).thenReturn(false);
        when(sessionService.executeInLocalViewWithParams(anyMap(), any())).thenAnswer(invocationOnMock -> {
            SessionExecutionBody sessionExecutionBody = invocationOnMock.getArgument(1);
            executionBodyResult[0] = sessionExecutionBody.execute();
            return executionBodyResult[0];
        });
        doReturn(comboitem).when(unit).createComboItem(any());
        unit.initCreateReturnRequestForm(mockedOrder);
        assertEquals(mockedOrder, unit.getOrder());
        verify(widgetInstanceManager).setTitle(MULTICAPTURE_CONFIRM_TITLE_LABEL + StringUtils.SPACE + TEST_ORDER_CODE);
        verify(unit).createComboItem(paymentTransactionEntryModel);
        assertEquals(ZERO_INDEX, transactions.getSelectedIndex());
        assertNull(executionBodyResult[0]);
    }

    @Test
    public void shouldConfirmWhenAmountIsEmpty() {
        beforeConfirm();
        when(amount.getValue()).thenReturn(StringUtils.EMPTY);
        testBlankAmount(ORDER_AMOUNT_ERROR_EMPTY, ORDER_AMOUNT_ERROR_EMPTY_LABEL);
    }

    @Test
    public void shouldConfirmWhenAmountIsWhitespace() {
        beforeConfirm();
        when(amount.getValue()).thenReturn(StringUtils.SPACE);
        testBlankAmount(ORDER_AMOUNT_ERROR_EMPTY, ORDER_AMOUNT_ERROR_EMPTY_LABEL);
    }

    @Test
    public void shouldConfirmWhenAmountIsNull() {
        beforeConfirm();
        when(amount.getValue()).thenReturn(null);
        testBlankAmount(ORDER_AMOUNT_ERROR_EMPTY, ORDER_AMOUNT_ERROR_EMPTY_LABEL);
    }

    @Test
    public void shouldConfirmWhenAmountHasWrongFormat() {
        beforeConfirm();
        when(amount.getValue()).thenReturn("WRONG_AMOUNT");
        testBlankAmount(ORDER_AMOUNT_ERROR_NUMBER_FORMAT, ORDER_AMOUNT_ERROR_NUMBER_FORMAT_LABEL);
    }

    @Test
    public void shouldConfirmWhenAmountIsZero() {
        beforeConfirm();
        String TEST_STRING_ZERO_AMOUNT = "0";
        when(amount.getValue()).thenReturn(TEST_STRING_ZERO_AMOUNT);
        testBlankAmount(ORDER_AMOUNT_ERROR_ZERO, ORDER_AMOUNT_ERROR_ZERO_LABEL);
    }

    @Test
    public void shouldConfirmWhenOrderIsNotNullAndHaveValidTransactionIdAndIsPartialCapturePossibleAndTransactionIsAccepted() throws Exception {
        beforeConfirm();
        when(brainTreePaymentTransactionService.isValidTransactionId(any(), any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isPartialCapturePossible(any())).thenReturn(true);
        when(brainTreeCheckoutFacade.isPayPalCheckout(any())).thenReturn(true);
        when(selectedTransactionEntry.getAmount()).thenReturn(BigDecimal.valueOf(TEST_TOTAL_ORDER_AMOUNT_GREATHER_THEN_CAPTURE_AMOUNT));
        when(amount.getValue()).thenReturn(TEST_PARTIAL_CAPTURE_AMOUNT);
        when(paymentTransactionEntryData.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(braintreeBackofficeMultiCaptureFacade.partialCapture(any(), any(), any(), anyBoolean())).thenReturn(paymentTransactionEntryData);
        when(widgetInstanceManager.getLabel(PARTIAL_CAPTURE_TITLE)).thenReturn(PARTIAL_CAPTURE_TITLE_LABEL);
        when(widgetInstanceManager.getLabel(PARTIAL_CAPTURE_SUCCESS)).thenReturn(PARTIAL_CAPTURE_SUCCESS_LABEL);
        doNothing().when(unit).showMsg(any(),any(),anyInt(),any());
        unit.confirm();
        verify(braintreeBackofficeMultiCaptureFacade).partialCapture(order, new BigDecimal(TEST_PARTIAL_CAPTURE_AMOUNT), selectedTransactionEntry, false);
        verify(unit).showMsg(PARTIAL_CAPTURE_SUCCESS_LABEL, PARTIAL_CAPTURE_TITLE_LABEL, Messagebox.OK, Messagebox.INFORMATION);
        verify(widgetInstanceManager).sendOutput(OUT_MODIFIED_ITEM, order);
    }

    @Test
    public void shouldConfirmWhenOrderIsNotNullAndHaveValidTransactionIdAndIsPartialCapturePossibleAndTransactionIsRejected() throws Exception {
        beforeConfirm();
        when(brainTreePaymentTransactionService.isValidTransactionId(any(), any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isPartialCapturePossible(any())).thenReturn(true);
        when(brainTreeCheckoutFacade.isPayPalCheckout(any())).thenReturn(true);
        when(selectedTransactionEntry.getAmount()).thenReturn(BigDecimal.valueOf(TEST_TOTAL_ORDER_AMOUNT_GREATHER_THEN_CAPTURE_AMOUNT));
        when(amount.getValue()).thenReturn(TEST_PARTIAL_CAPTURE_AMOUNT);
        when(paymentTransactionEntryData.getTransactionStatus()).thenReturn(TransactionStatus.REJECTED.name());
        when(paymentTransactionEntryData.getTransactionStatusDetails()).thenReturn(TRANSACTION_STATUS_LABEL);
        when(braintreeBackofficeMultiCaptureFacade.partialCapture(any(), any(), any(), anyBoolean())).thenReturn(paymentTransactionEntryData);
        when(widgetInstanceManager.getLabel(PARTIAL_CAPTURE_TITLE)).thenReturn(PARTIAL_CAPTURE_TITLE_LABEL);
        when(widgetInstanceManager.getLabel(PARTIAL_CAPTURE_ERROR)).thenReturn(PARTIAL_CAPTURE_ERROR_LABEL);
        doNothing().when(unit).showMsg(any(),any(),anyInt(),any());
        unit.confirm();
        verify(braintreeBackofficeMultiCaptureFacade).partialCapture(order, new BigDecimal(TEST_PARTIAL_CAPTURE_AMOUNT), selectedTransactionEntry, false);
        verify(unit).showMsg(PARTIAL_CAPTURE_ERROR_LABEL + ". "
                + TRANSACTION_STATUS_LABEL, PARTIAL_CAPTURE_TITLE_LABEL, Messagebox.OK, Messagebox.ERROR);
        verify(widgetInstanceManager).sendOutput(OUT_MODIFIED_ITEM, order);
    }

    @Test
    public void shouldConfirmWhenOrderIsNotNullAndHaveValidTransactionIdAndCaptureAmountEqualsTotalAndSubmitForSettlementAvailable() throws Exception {
        beforeConfirm();
        when(brainTreePaymentTransactionService.isValidTransactionId(any(), any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isPartialCapturePossible(any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isSubmitForSettlementAvailable(any())).thenReturn(true);
        when(brainTreeCheckoutFacade.isPayPalCheckout(any())).thenReturn(true);
        when(selectedTransactionEntry.getAmount()).thenReturn(BigDecimal.valueOf(Double.parseDouble(TEST_PARTIAL_CAPTURE_AMOUNT)));
        when(amount.getValue()).thenReturn(TEST_PARTIAL_CAPTURE_AMOUNT);
        doNothing().when(unit).showMsg(any(),any(),anyInt(),any());
        when(braintreeBackofficeMultiCaptureFacade.submitForSettlement(any(), any(), any(), any(Boolean.class))).thenReturn(paymentTransactionEntryData);
        unit.confirm();
        verify(braintreeBackofficeMultiCaptureFacade).submitForSettlement(order, new BigDecimal(TEST_PARTIAL_CAPTURE_AMOUNT), selectedTransactionEntry, false);
        verify(widgetInstanceManager).sendOutput(OUT_MODIFIED_ITEM, order);
    }

    @Test
    public void shouldConfirmWhenOrderIsNotNullAndHaveValidTransactionIdAndCaptureAmountEqualsTotalAndCanPerformDelayedCapture() throws Exception {
        beforeConfirm();
        when(brainTreePaymentTransactionService.isValidTransactionId(any(), any())).thenReturn(true);
        when(brainTreePaymentTransactionService.canPerformDelayedCapture(any(), any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isPartialCapturePossible(any())).thenReturn(true);
        when(braintreeBackofficeMultiCaptureFacade.isSubmitForSettlementAvailable(any())).thenReturn(false);
        when(brainTreeCheckoutFacade.isPayPalCheckout(any())).thenReturn(true);
        when(selectedTransactionEntry.getAmount()).thenReturn(BigDecimal.valueOf(Double.parseDouble(TEST_PARTIAL_CAPTURE_AMOUNT)));
        when(amount.getValue()).thenReturn(TEST_PARTIAL_CAPTURE_AMOUNT);
        when(brainTreeCheckoutFacade.capturePayment(any(), any(), any(), any(Boolean.class))).thenReturn(paymentTransactionEntryData);
        doNothing().when(unit).showMsg(any(),any(),anyInt(),any());
        unit.confirm();
        verify(brainTreeCheckoutFacade).capturePayment(order, new BigDecimal(TEST_PARTIAL_CAPTURE_AMOUNT), selectedTransactionEntry, false);
        verify(widgetInstanceManager).sendOutput(OUT_MODIFIED_ITEM, order);
    }

    private void beforeConfirm() {
        final Object[] executionBodyResult = new Object[1];
        when(order.getSite()).thenReturn(baseSiteModel);
        when(checkbox.isChecked()).thenReturn(false);
        when(transactions.getSelectedIndex()).thenReturn(ZERO_INDEX);
        when(transactions.getSelectedItem()).thenReturn(comboitem);
        when(comboitem.getLabel()).thenReturn(TEST_COMBOITEM_LABEL);
        when(comboitem.getValue()).thenReturn(selectedTransactionEntry);
        when(braintreeBackofficeMultiCaptureFacade.getAuthorizationAmountByTransactionId(any(), any())).thenReturn(new BigDecimal(TEST_PARTIAL_CAPTURE_AMOUNT));
        when(sessionService.executeInLocalViewWithParams(anyMap(), any())).thenAnswer(invocationOnMock -> {
            SessionExecutionBody sessionExecutionBody = invocationOnMock.getArgument(1);
            executionBodyResult[0] = sessionExecutionBody.execute();
            return executionBodyResult[0];
        });
    }

    private void testBlankAmount(String labelName, String label) {
        when(widgetInstanceManager.getLabel(labelName)).thenReturn(label);
        WrongValueException throwable = Assert.assertThrows(WrongValueException.class, () -> unit.confirm());
        assertEquals(label, throwable.getMessage());
    }

}
