package com.braintree.auth;

import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.jalo.user.User;
import de.hybris.platform.servicelayer.user.UserService;
import de.hybris.platform.spring.security.CoreUserDetails;
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.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.any;

import static org.junit.Assert.assertEquals;

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

    private static final String MY_USER_PRINCIPAL = "MY_USER_PRINCIPAL";
    private static final String CUSTOMER_NAME = "CUSTOMER_NAME";
    private static final String PAYPAL_ACCESS_TOKEN = "PAYPAL_ACCESS_TOKEN";
    private static final String USER_NAME = "USER_NAME";
    private static final String CREDENTIAL = "CREDENTIAL";
    private static final String NONE_PROVIDED = "NONE_PROVIDED";

    @Mock
    private AbstractAuthenticationToken authentication;
    @Mock
    private CustomerModel customerModel;
    @Mock
    private UserDetails userDetails;
    @Mock
    private UserDetailsChecker userDetailsChecker;
    @Mock
    private User user;
    @Mock
    private ConnectWithPayPalAuthenticationToken authenticationToken;

    @Mock
    private UserService userService;

    @Mock
    private UserDetailsService userDetailsService;

    @Spy
    @InjectMocks
    private PayPalAuthProvider unit;

    @Test
    public void shouldAuthenticate() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenReturn(customerModel);

        when(customerModel.getPayPalAccessToken()).thenReturn(PAYPAL_ACCESS_TOKEN);

        when(userDetailsService.loadUserByUsername(CUSTOMER_NAME)).thenReturn(userDetails);

        doReturn(userDetailsChecker).when(unit).getPreAuthenticationChecks();
        doNothing().when(userDetailsChecker).check(userDetails);
        when(userDetails.getUsername()).thenReturn(USER_NAME);
        when(userDetails.isCredentialsNonExpired()).thenReturn(true);
        doReturn(user).when(unit).getUserManagerInstance(USER_NAME);
        when(authentication.getCredentials()).thenReturn(PAYPAL_ACCESS_TOKEN);
        doNothing().when(unit).setUserToCurrentSession(user);
        doReturn(authenticationToken).when(unit).createSuccessAuthentication(authentication, userDetails);

        final Authentication result = unit.authenticate(authentication);

        assertEquals(authenticationToken, result);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();

        verify(userService).getUserForUID(CUSTOMER_NAME);

        verify(customerModel, times(2)).getPayPalAccessToken();

        verify(userDetailsService).loadUserByUsername(CUSTOMER_NAME);

        verify(unit).getPreAuthenticationChecks();
        verify(userDetailsChecker).check(userDetails);
        verify(userDetails).getUsername();
        verify(userDetails).isCredentialsNonExpired();
        verify(unit).getUserManagerInstance(USER_NAME);
        verify(authentication).getCredentials();
        verify(unit).setUserToCurrentSession(user);
        verify(unit).createSuccessAuthentication(authentication, userDetails);
    }

    @Test(expected = BadCredentialsException.class)
    public void shouldThrowBadCredentialsExceptionBecauseAccessTokenIsNotCorrect() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenReturn(customerModel);

        when(customerModel.getPayPalAccessToken()).thenReturn(PAYPAL_ACCESS_TOKEN);

        when(userDetailsService.loadUserByUsername(CUSTOMER_NAME)).thenReturn(userDetails);

        doReturn(userDetailsChecker).when(unit).getPreAuthenticationChecks();
        doNothing().when(userDetailsChecker).check(userDetails);
        when(userDetails.getUsername()).thenReturn(USER_NAME);
        doReturn(user).when(unit).getUserManagerInstance(USER_NAME);
        when(authentication.getCredentials()).thenReturn(CREDENTIAL);

        unit.authenticate(authentication);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();

        verify(userService).getUserForUID(CUSTOMER_NAME);

        verify(customerModel, times(2)).getPayPalAccessToken();

        verify(userDetailsService).loadUserByUsername(CUSTOMER_NAME);

        verify(unit).getPreAuthenticationChecks();
        verify(userDetailsChecker).check(userDetails);
        verify(userDetails).getUsername();
        verify(unit).getUserManagerInstance(USER_NAME);
        verify(authentication).getCredentials();
    }

    @Test(expected = BadCredentialsException.class)
    public void shouldThrowBadCredentialsExceptionBecauseAccessTokenIsNotString() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenReturn(customerModel);

        when(customerModel.getPayPalAccessToken()).thenReturn(PAYPAL_ACCESS_TOKEN);

        when(userDetailsService.loadUserByUsername(CUSTOMER_NAME)).thenReturn(userDetails);

        doReturn(userDetailsChecker).when(unit).getPreAuthenticationChecks();
        doNothing().when(userDetailsChecker).check(userDetails);
        when(userDetails.getUsername()).thenReturn(USER_NAME);
        doReturn(user).when(unit).getUserManagerInstance(USER_NAME);
        when(authentication.getCredentials()).thenReturn(new Object());

        unit.authenticate(authentication);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();

        verify(userService).getUserForUID(CUSTOMER_NAME);

        verify(customerModel, times(2)).getPayPalAccessToken();

        verify(userDetailsService).loadUserByUsername(CUSTOMER_NAME);

        verify(unit).getPreAuthenticationChecks();
        verify(userDetailsChecker).check(userDetails);
        verify(userDetails).getUsername();
        verify(unit).getUserManagerInstance(USER_NAME);
        verify(authentication).getCredentials();
    }

    @Test(expected = CredentialsExpiredException.class)
    public void shouldThrowCredentialsExpiredException() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenReturn(customerModel);

        when(customerModel.getPayPalAccessToken()).thenReturn(PAYPAL_ACCESS_TOKEN);

        when(userDetailsService.loadUserByUsername(CUSTOMER_NAME)).thenReturn(userDetails);

        doReturn(userDetailsChecker).when(unit).getPreAuthenticationChecks();
        doNothing().when(userDetailsChecker).check(userDetails);
        when(userDetails.getUsername()).thenReturn(USER_NAME);
        when(userDetails.isCredentialsNonExpired()).thenReturn(false);
        doReturn(user).when(unit).getUserManagerInstance(USER_NAME);
        when(authentication.getCredentials()).thenReturn(PAYPAL_ACCESS_TOKEN);

        unit.authenticate(authentication);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();

        verify(userService).getUserForUID(CUSTOMER_NAME);

        verify(customerModel, times(2)).getPayPalAccessToken();

        verify(userDetailsService).loadUserByUsername(CUSTOMER_NAME);

        verify(unit).getPreAuthenticationChecks();
        verify(userDetailsChecker).check(userDetails);
        verify(userDetails).getUsername();
        verify(userDetails).isCredentialsNonExpired();
        verify(unit).getUserManagerInstance(USER_NAME);
        verify(authentication).getCredentials();
    }

    @Test
    public void shouldCallFirstSuperAuthenticate() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenReturn(customerModel);

        when(customerModel.getPayPalAccessToken()).thenReturn("");

        doReturn(authenticationToken).when(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));

        final Authentication result = unit.authenticate(authentication);

        assertEquals(authenticationToken, result);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();

        verify(userService).getUserForUID(CUSTOMER_NAME);

        verify(customerModel).getPayPalAccessToken();

        verify(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));
    }

    @Test
    public void shouldCallSecondSuperAuthenticate() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(NONE_PROVIDED);

        doReturn(authenticationToken).when(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));

        final Authentication result = unit.authenticate(authentication);

        assertEquals(authenticationToken, result);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();

        verify(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));
    }

    @Test(expected = BadCredentialsException.class)
    public void shouldThrowBadCredentialsException() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(true).when(unit).isSystemInitialized();

        when(authentication.getPrincipal()).thenReturn(MY_USER_PRINCIPAL);
        when(authentication.getName()).thenReturn(CUSTOMER_NAME);

        when(userService.getUserForUID(CUSTOMER_NAME)).thenThrow(new UsernameNotFoundException(""));

        unit.authenticate(authentication);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(authentication).getPrincipal();
        verify(authentication).getName();
        verify(userService).getUserForUID(CUSTOMER_NAME);
    }

    @Test
    public void shouldCallCreateSuccessAuthenticationBecauseHasCurrentTenantFalse() {
        doReturn(false).when(unit).hasCurrentTenant();

        doReturn(authenticationToken).when(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));

        final Authentication result = unit.authenticate(authentication);

        assertEquals(authenticationToken, result);

        verify(unit).hasCurrentTenant();

        verify(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));
    }

    @Test
    public void shouldCallCreateSuccessAuthenticationBecauseIsSystemInitializedFalse() {
        doReturn(true).when(unit).hasCurrentTenant();
        doReturn(false).when(unit).isSystemInitialized();

        doReturn(authenticationToken).when(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));

        final Authentication result = unit.authenticate(authentication);

        assertEquals(authenticationToken, result);

        verify(unit).hasCurrentTenant();
        verify(unit).isSystemInitialized();

        verify(unit).createSuccessAuthentication(any(Authentication.class), any(CoreUserDetails.class));
    }

    @Test
    public void shouldCreateSuccessAuthentication() {
        final UserDetails user = mock(UserDetails.class);
        final Object details = mock(Object.class);

        when(user.getUsername()).thenReturn(USER_NAME);
        when(authentication.getDetails()).thenReturn(details);

        final Authentication result = unit.createSuccessAuthentication(authentication, user);

        assertEquals(USER_NAME, result.getPrincipal());
        assertEquals(details, result.getDetails());
    }

}
