package com.atlassian.stash.internal.sal.user;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.auth.IncorrectPasswordAuthenticationException;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.sal.api.user.UserKey;
import com.atlassian.sal.api.user.UserProfile;
import com.atlassian.bitbucket.avatar.AvatarService;
import com.atlassian.bitbucket.user.NoSuchUserException;
import com.atlassian.stash.internal.user.InternalUserService;
import com.atlassian.bitbucket.nav.NavBuilder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultUserManagerTest {

    private static final int USER_ID = 99;
    private static final String USER_NAME = "user";

    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private AvatarService avatarService;
    @Mock
    private PermissionService permissionService;
    @Mock
    private NavBuilder navBuilder;
    private ApplicationUser user;
    @InjectMocks
    private DefaultUserManager userManager;
    @Mock
    private InternalUserService userService;

    @Before
    public void setUp() throws Exception {
        user = mockUser(USER_ID, USER_NAME);
        when(userService.getUserById(eq(USER_ID))).thenReturn(user);
        when(userService.getUserByName(eq(USER_NAME))).thenReturn(user);
        when(authenticationContext.isAuthenticated()).thenReturn(Boolean.TRUE);
        when(authenticationContext.getCurrentUser()).thenReturn(user);

        when(userService.getUserByKey(eq(UserKey.fromLong(USER_ID)))).thenReturn(user);
        when(userService.getUserByKey(eq(new UserKey(USER_NAME)))).thenReturn(user);
    }

    @Test
    public void testAuthenticate() throws Exception {
        when(userService.authenticate(eq(USER_NAME), eq("password"))).thenReturn(user);
        assertTrue(userManager.authenticate(USER_NAME, "password"));
        verify(userService).authenticate(USER_NAME, "password");
    }

    @Test
    public void testAuthenticateDoesNotRethrowAuthenticationExceptions() throws Exception {
        doThrow(IncorrectPasswordAuthenticationException.class)
                .when(userService).authenticate(eq(USER_NAME), eq("password"));
        assertFalse(userManager.authenticate(USER_NAME, "password"));
    }

    @Test
    public void testAuthenticateDoesNotRethrowNoSuchUserExceptions() throws Exception {
        doThrow(NoSuchUserException.class)
                .when(userService).authenticate(eq(USER_NAME), eq("password"));
        assertFalse(userManager.authenticate(USER_NAME, "password"));
    }

    @Test
    public void testGetRemoteUser() throws Exception {
        UserProfile expected = new DefaultUserProfile(user, navBuilder, avatarService);
        assertEquals(expected, userManager.getRemoteUser());
    }

    @Test
    public void testGetRemoteUserNotAuthenticated() throws Exception {
        mockNotAuthenticated();
        assertNull(userManager.getRemoteUser());
    }

    @Test
    public void testGetRemoteUserElevatedPermissions() throws Exception {
        mockNotAuthenticatedElevated();
        assertNull(userManager.getRemoteUser());
    }

    @Test
    public void testGetRemoteUsername() throws Exception {
        assertEquals(USER_NAME, userManager.getRemoteUsername());
    }

    @Test
    public void testGetRemoteUsernameNotAuthenticated() throws Exception {
        mockNotAuthenticated();
        assertNull(userManager.getRemoteUsername());
    }

    @Test
    public void testGetRemoteUsernameElevatedPermissions() throws Exception {
        mockNotAuthenticatedElevated();
        assertNull(userManager.getRemoteUsername());
    }

    @Test
    public void testGetRemoteUsernameFromRequest() throws Exception {
        assertEquals(USER_NAME, userManager.getRemoteUsername(new MockHttpServletRequest()));
    }

    @Test
    public void testGetRemoteUserKey() throws Exception {
        assertEquals(UserKey.fromLong(USER_ID), userManager.getRemoteUserKey());
    }

    @Test
    public void testGetRemoteUserKeyNotAuthenticated() throws Exception {
        mockNotAuthenticated();
        assertNull(userManager.getRemoteUserKey());
    }

    @Test
    public void testGetRemoteUserKeyElevatedPermissions() throws Exception {
        mockNotAuthenticatedElevated();
        assertNull(userManager.getRemoteUserKey());
    }

    @Test
    public void testGetRemoteUserKeyFromRequest() throws Exception {
        assertEquals(UserKey.fromLong(USER_ID), userManager.getRemoteUserKey(new MockHttpServletRequest()));
    }

    @Test
    public void testGetUserProfileByKey() throws Exception {
        UserProfile expected = new DefaultUserProfile(user, navBuilder, avatarService);
        assertEquals(expected, userManager.getUserProfile(UserKey.fromLong(USER_ID)));
    }

    @Test
    public void testGetUserProfileByKeyNull() throws Exception {
        assertNull(userManager.getUserProfile((UserKey) null));
    }

    @Test
    public void testGetUserProfileByKeyNotFound() throws Exception {
        assertNull(userManager.getUserProfile(UserKey.fromLong(-1)));
    }

    @Test
    public void testGetUserProfileByName() throws Exception {
        UserProfile expected = new DefaultUserProfile(user, navBuilder, avatarService);
        assertEquals(expected, userManager.getUserProfile(USER_NAME));
    }

    @Test
    public void testGetUserProfileByKeyBackedWithName() throws Exception {
        UserProfile expected = new DefaultUserProfile(user, navBuilder, avatarService);
        assertEquals(expected, userManager.getUserProfile(new UserKey(USER_NAME)));
    }

    @Test
    public void testGetUserProfileByNameNull() throws Exception {
        assertNull(userManager.getUserProfile((String) null));
    }

    @Test
    public void testGetUserProfileByNameNotFound() throws Exception {
        assertNull(userManager.getUserProfile("not found"));
    }

    @Test
    public void testIsAdminByName() throws Exception {
        mockUserPermission(mockUser("admin"), Permission.ADMIN);
        assertTrue(userManager.isAdmin("admin"));
    }

    @Test
    public void testIsAdminByNameNull() {
        assertFalse(userManager.isAdmin((String) null));
    }

    @Test
    public void testIsAdminByNameUnknown() throws Exception {
        assertFalse(userManager.isAdmin(USER_NAME));
    }

    @Test
    public void testIsAdminByKey() throws Exception {
        ApplicationUser adminUser = mockUser(123, "admin");

        when(userService.getUserByKey(eq(UserKey.fromLong(123)))).thenReturn(adminUser);
        mockUserPermission(adminUser, Permission.ADMIN);

        assertTrue(userManager.isAdmin(UserKey.fromLong(123)));
    }

    @Test
    public void testIsAdminByKeyNull() {
        assertFalse(userManager.isAdmin((String) null));
    }

    @Test
    public void testIsAdminByKeyUnknown() throws Exception {
        assertFalse(userManager.isAdmin(USER_NAME));
    }

    @Test
    public void testIsSystemAdminByName() throws Exception {
        mockUserPermission(mockUser("sysadmin"), Permission.SYS_ADMIN);
        assertTrue(userManager.isSystemAdmin("sysadmin"));
    }

    @Test
    public void testIsSystemAdminByNameNull() throws Exception {
        assertFalse(userManager.isSystemAdmin((String) null));
    }

    @Test
    public void testIsSystemAdminByNameUnknown() throws Exception {
        assertFalse(userManager.isSystemAdmin(USER_NAME));
    }

    @Test
    public void testIsUserInGroupByKey() throws Exception {
        when(userService.isUserInGroup(eq(user), eq("group"))).thenReturn(true);
        assertTrue(userManager.isUserInGroup(UserKey.fromLong(USER_ID), "group"));
        assertFalse(userManager.isUserInGroup(UserKey.fromLong(USER_ID), "othergroup"));
    }

    @Test
    public void testIsUserInGroupByKeyNullKey() {
        assertFalse(userManager.isUserInGroup((UserKey) null, "group"));
    }

    @Test
    public void testIsUserInGroupByName() throws Exception {
        when(userService.isUserInGroup(USER_NAME, "group")).thenReturn(true);
        assertTrue(userManager.isUserInGroup(USER_NAME, "group"));
        assertFalse(userManager.isUserInGroup(USER_NAME, "othergroup"));
        assertFalse(userManager.isUserInGroup("otherName", "group"));
    }

    @Test
    public void testIsUserInGroupByNameNullUser() {
        assertFalse(userManager.isUserInGroup((String) null, "group"));
    }

    @Test
    public void testResolve() throws Exception {
        final ApplicationUser user = mockUser("user2");
        when(userService.getUserByName("user2")).thenReturn(user);
        assertEquals("user2", userManager.resolve("user2").getName());
    }

    @Test
    public void testResolveNull() throws Exception {
        assertNull(userManager.resolve(null));
    }

    @Test
    public void testResolveUnknown() throws Exception {
        assertNull(userManager.resolve("user3"));
    }

    private void mockNotAuthenticated() {
        reset(authenticationContext);
        when(authenticationContext.isAuthenticated()).thenReturn(false);
    }

    private void mockNotAuthenticatedElevated() {
        mockNotAuthenticated();
        when(authenticationContext.getCurrentUser()).thenReturn(null);
    }

    private void mockUserPermission(ApplicationUser user, Permission permission) {
        when(userService.getUserByName(eq(user.getName()))).thenReturn(user);
        if (user.getId() != 0) {
            when(userService.getUserById(eq(user.getId()))).thenReturn(user);
        }
        when(permissionService.hasGlobalPermission(eq(user), eq(permission))).thenReturn(true);
        when(permissionService.hasGlobalPermission(eq(user.getName()), eq(permission))).thenReturn(true);
    }

    private ApplicationUser mockUser(String name) {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getName()).thenReturn(name);

        return user;
    }

    private ApplicationUser mockUser(int id, String name) {
        ApplicationUser user = mockUser(name);
        when(user.getId()).thenReturn(id);

        return user;
    }

}
