package com.braintree.b2bfacades;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.exceptions.PaypalAccessTokenNotFoundException;
import com.braintree.facade.BrainTreeRegistrationUserFacade;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserData;
import com.braintree.hybris.data.BrainTreeConnectWithPayPalUserExistB2BData;
import com.braintree.hybris.data.BraintreeConnectWithPayPalConnectB2BRegisterData;
import com.braintree.model.PaypalAccessTokenModel;
import com.braintree.service.BrainTreeAccessTokenService;
import com.braintree.service.BrainTreeConnectWithPayPalService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.cms2.servicelayer.services.CMSSiteService;
import de.hybris.platform.commerceservices.customer.DuplicateUidException;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.core.model.user.EmployeeModel;
import de.hybris.platform.core.model.user.TitleModel;
import de.hybris.platform.b2b.model.B2BRegistrationModel;
import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException;
import de.hybris.platform.servicelayer.i18n.CommonI18NService;
import de.hybris.platform.servicelayer.model.ItemModelContext;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.user.UserService;
import de.hybris.platform.store.services.BaseStoreService;
import de.hybris.platform.tx.Transaction;
import de.hybris.platform.workflow.WorkflowProcessingService;
import de.hybris.platform.workflow.WorkflowService;
import de.hybris.platform.workflow.WorkflowTemplateService;
import de.hybris.platform.workflow.model.WorkflowModel;
import de.hybris.platform.workflow.model.WorkflowTemplateModel;
import org.apache.commons.lang3.StringUtils;
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 java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class DefaultBraintreeB2BRegistrationUserFacadeTest {

    private static final String ACCESS_TOKEN_GUID = "accessTokenGuid";
    private static final String ACCESS_TOKEN = "accessToken";
    private static final String EMAIL = "email";
    private static final String REGISTRATION_WORKFLOW = "B2BUserRegistration";
    private static final String PAYER_ID = "payerId";
    private static final String TITLE = "title";
    private static final String B2B_CUSTOMER_TYPE = "B2BCustomer";
    private static final String CUSTOMER_TYPE = "Customer";

    @Mock
    private BrainTreeConnectWithPayPalService brainTreeConnectWithPayPalService;
    @Mock
    private ModelService modelService;
    @Mock
    private UserService userService;
    @Mock
    private CommonI18NService commonI18NService;
    @Mock
    private WorkflowTemplateService workflowTemplateService;
    @Mock
    private WorkflowService workflowService;
    @Mock
    private WorkflowProcessingService workflowProcessingService;
    @Mock
    private BaseStoreService baseStoreService;
    @Mock
    private CMSSiteService cmsSiteService;
    @Mock
    private BrainTreeRegistrationUserFacade registrationUserFacade;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;
    @Mock
    private BrainTreeAccessTokenService braintreeAccessTokenService;

    @Spy
    @InjectMocks
    private DefaultBraintreeB2BRegistrationUserFacade unit;

    @Mock
    private PaypalAccessTokenModel paypalAccessToken;
    @Mock
    private Transaction transaction;
    @Mock
    private B2BRegistrationModel b2bRegistration;
    @Mock
    private CustomerModel customer;
    @Mock
    private WorkflowTemplateModel workflowTemplate;
    @Mock
    private EmployeeModel employee;
    @Mock
    private WorkflowModel workflow;
    @Mock
    private TitleModel title;
    @Mock
    private ItemModelContext itemModelContext;

    @Test(expected = PaypalAccessTokenNotFoundException.class)
    public void shouldThrowPaypalAccessTokenNotFoundExceptionIfNoTokenForGuidExist() throws DuplicateUidException {
        BraintreeConnectWithPayPalConnectB2BRegisterData data = new BraintreeConnectWithPayPalConnectB2BRegisterData();
        data.setAccessTokenGuid(ACCESS_TOKEN_GUID);

        when(braintreeAccessTokenService.getPaypalAccessToken(ACCESS_TOKEN_GUID)).thenReturn(Optional.empty());

        unit.registerB2BPayPalUser(data);
    }

    @Test
    public void shouldThrowDuplicateUidExceptionIfUserAlreadyExistExist() throws DuplicateUidException {
        doReturn(transaction).when(unit).getCurrentTransaction();

        BraintreeConnectWithPayPalConnectB2BRegisterData data = new BraintreeConnectWithPayPalConnectB2BRegisterData();
        data.setAccessTokenGuid(ACCESS_TOKEN_GUID);
        BrainTreeConnectWithPayPalUserData userData = new BrainTreeConnectWithPayPalUserData();

        when(braintreeAccessTokenService.getPaypalAccessToken(ACCESS_TOKEN_GUID)).thenReturn(Optional.of(paypalAccessToken));
        when(brainTreeConnectWithPayPalService.getUserDataByAccessToken(ACCESS_TOKEN)).thenReturn(userData);
        when(registrationUserFacade.getEmailFromPayPalUserData(userData)).thenReturn(EMAIL);
        when(userService.isUserExisting(EMAIL)).thenReturn(true);

        when(paypalAccessToken.getAccessToken()).thenReturn(ACCESS_TOKEN);

        String result = unit.registerB2BPayPalUser(data);

        assertEquals(StringUtils.EMPTY, result);
    }

    @Test
    public void shouldRegisterB2BPayPalUser() throws DuplicateUidException {
        doReturn(transaction).when(unit).getCurrentTransaction();

        BraintreeConnectWithPayPalConnectB2BRegisterData data = new BraintreeConnectWithPayPalConnectB2BRegisterData();
        data.setAccessTokenGuid(ACCESS_TOKEN_GUID);
        BrainTreeConnectWithPayPalUserData userData = new BrainTreeConnectWithPayPalUserData();
        userData.setPayerId(PAYER_ID);

        when(braintreeAccessTokenService.getPaypalAccessToken(ACCESS_TOKEN_GUID)).thenReturn(Optional.of(paypalAccessToken));
        when(brainTreeConnectWithPayPalService.getUserDataByAccessToken(ACCESS_TOKEN)).thenReturn(userData);
        when(registrationUserFacade.getEmailFromPayPalUserData(userData)).thenReturn(EMAIL);
        when(userService.isUserExisting(EMAIL)).thenReturn(false);
        when(userService.getAdminUser()).thenReturn(employee);
        when(modelService.create(B2BRegistrationModel.class)).thenReturn(b2bRegistration);
        when(modelService.create(CustomerModel.class)).thenReturn(customer);
        when(workflowTemplateService.getWorkflowTemplateForCode(REGISTRATION_WORKFLOW)).thenReturn(workflowTemplate);
        when(workflowService.createWorkflow(workflowTemplate, b2bRegistration, employee)).thenReturn(workflow);
        when(brainTreeConfigService.isPayPalConnectAddPaymentMethodFlow()).thenReturn(true);

        when(paypalAccessToken.getAccessToken()).thenReturn(ACCESS_TOKEN);

        String result = unit.registerB2BPayPalUser(data);

        verify(modelService).save(b2bRegistration);
        verify(modelService).save(customer);
        verify(modelService).save(workflow);
        verify(workflowProcessingService).startWorkflow(workflow);
        assertEquals(PAYER_ID, result);
    }

    @Test
    public void shouldRemoveAccessTokenIfAddPaymentMethodFlowFalse() throws DuplicateUidException {
        doReturn(transaction).when(unit).getCurrentTransaction();

        BraintreeConnectWithPayPalConnectB2BRegisterData data = new BraintreeConnectWithPayPalConnectB2BRegisterData();
        data.setAccessTokenGuid(ACCESS_TOKEN_GUID);
        data.setTitleCode(TITLE);
        BrainTreeConnectWithPayPalUserData userData = new BrainTreeConnectWithPayPalUserData();
        userData.setPayerId(PAYER_ID);

        when(braintreeAccessTokenService.getPaypalAccessToken(ACCESS_TOKEN_GUID)).thenReturn(Optional.of(paypalAccessToken));
        when(brainTreeConnectWithPayPalService.getUserDataByAccessToken(ACCESS_TOKEN)).thenReturn(userData);
        when(registrationUserFacade.getEmailFromPayPalUserData(userData)).thenReturn(EMAIL);
        when(userService.isUserExisting(EMAIL)).thenReturn(false);
        when(userService.getAdminUser()).thenReturn(employee);
        when(userService.getTitleForCode(TITLE)).thenReturn(title);
        when(modelService.create(B2BRegistrationModel.class)).thenReturn(b2bRegistration);
        when(modelService.create(CustomerModel.class)).thenReturn(customer);
        when(workflowTemplateService.getWorkflowTemplateForCode(REGISTRATION_WORKFLOW)).thenReturn(workflowTemplate);
        when(workflowService.createWorkflow(workflowTemplate, b2bRegistration, employee)).thenReturn(workflow);
        when(brainTreeConfigService.isPayPalConnectAddPaymentMethodFlow()).thenReturn(false);

        when(paypalAccessToken.getAccessToken()).thenReturn(ACCESS_TOKEN);

        String result = unit.registerB2BPayPalUser(data);

        verify(b2bRegistration).setTitle(title);
        verify(customer).setTitle(title);
        verify(modelService).save(b2bRegistration);
        verify(modelService).save(customer);
        verify(modelService).save(workflow);
        verify(workflowProcessingService).startWorkflow(workflow);
        verify(braintreeAccessTokenService).removePaypalAccessToken(ACCESS_TOKEN_GUID);
        assertEquals(PAYER_ID, result);
    }

    @Test
    public void shouldReturnFalseIfCustomerDoesNotExist() {
        when(userService.getUserForUID(EMAIL)).thenThrow(new UnknownIdentifierException(StringUtils.EMPTY));

        BrainTreeConnectWithPayPalUserExistB2BData result = unit.isCustomerExistAndApprovedByUID(EMAIL);

        assertFalse(result.isExist());
        assertFalse(result.isApproved());
    }

    @Test
    public void shouldReturnTrueIfCustomerExistAndFalseIfNotApproved() {
        when(userService.getUserForUID(EMAIL)).thenReturn(customer);
        when(customer.getItemModelContext()).thenReturn(itemModelContext);
        when(itemModelContext.getItemType()).thenReturn(CUSTOMER_TYPE);

        BrainTreeConnectWithPayPalUserExistB2BData result = unit.isCustomerExistAndApprovedByUID(EMAIL);

        assertTrue(result.isExist());
        assertFalse(result.isApproved());
    }

    @Test
    public void shouldReturnTrueIfCustomerExistAndTrueIfApproved() {
        when(userService.getUserForUID(EMAIL)).thenReturn(customer);
        when(customer.getItemModelContext()).thenReturn(itemModelContext);
        when(itemModelContext.getItemType()).thenReturn(B2B_CUSTOMER_TYPE);

        BrainTreeConnectWithPayPalUserExistB2BData result = unit.isCustomerExistAndApprovedByUID(EMAIL);

        assertTrue(result.isExist());
        assertTrue(result.isApproved());
    }

}