package com.atlassian.stash.internal.user;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.permission.*;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserType;
import com.atlassian.crowd.embedded.api.CrowdService;
import com.atlassian.crowd.embedded.impl.ImmutableUser;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.bitbucket.AbstractServiceTest;
import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.stash.experimental.user.PermittedGroupGlobalSearchRequest;
import com.atlassian.stash.experimental.user.PermittedUserSearchRequest;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.stash.internal.project.InternalNormalProject;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.bitbucket.license.LicenseService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import org.apache.commons.lang3.StringUtils;
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 java.util.function.Function;

import static com.atlassian.bitbucket.hamcrest.PageMatchers.*;
import static com.atlassian.bitbucket.hamcrest.PermittedGroupMatchers.permittedGroup;
import static com.atlassian.bitbucket.hamcrest.PermittedUserMatchers.permittedUser;
import static com.atlassian.bitbucket.hamcrest.StashUserMatchers.userWithId;
import static com.atlassian.bitbucket.hamcrest.StashUserMatchers.userWithName;
import static com.atlassian.bitbucket.mockito.PageAnswer.withLastPageOf;
import static com.atlassian.bitbucket.mockito.PagesAnswer.withPagesUpTo;
import static com.atlassian.bitbucket.permission.Permission.*;
import static com.atlassian.bitbucket.util.PageUtils.newRequest;
import static org.hamcrest.Matchers.not;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;

/**
 * Unit tests for search API in {@link DefaultPermissionAdminService}.
 */
@RunWith(MockitoJUnitRunner.class)
public class DefaultPermissionAdminServiceSearchTest extends AbstractServiceTest {

    private static final Function<Integer, ApplicationUser> ACTIVE_USER_GENERATOR =
            DefaultPermissionAdminServiceSearchTest::createActiveUser;

    private static final Function<Integer, PermittedUser> PERMITTED_USER_GENERATOR =
            id -> createActivePermittedUser(id, LICENSED_USER);

    private static final Function<Integer, String> GROUP_GENERATOR =
            DefaultPermissionAdminServiceSearchTest::groupNameFor;

    private static final Function<Integer, PermittedGroup> PERMITTED_GROUP_GENERATOR =
            id -> new InternalPermittedGroup(groupNameFor(id), LICENSED_USER.getWeight());

    private static final InternalProject TEST_PROJECT = new InternalNormalProject.Builder()
            .id(1)
            .key("TEST")
            .name("Test Project")
            .build();
    private static final InternalRepository TEST_REPOSITORY = new InternalRepository.Builder()
            .id(1)
            .name("Test Repository")
            .project(TEST_PROJECT)
            .build();
    private static final InternalApplicationUser TEST_USER = new InternalNormalUser.Builder().id(1).name("Tester").build();

    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private CrowdService crowdService;
    @Mock
    private EventPublisher eventPublisher;
    @Mock
    private GlobalPermissionDao globalPermissionDao;
    @Mock
    private ProjectPermissionDao projectPermissionDao;
    @Mock
    private RepositoryPermissionDao repositoryPermissionDao;
    @Mock
    private I18nService i18nService;
    @Mock
    private LicenseService licenseService;
    @Mock
    private PermissionService permissionService;
    @Mock
    private InternalUserService userService;
    @Mock
    private PermissionValidationService permissionValidationService;

    @InjectMocks
    private DefaultPermissionAdminService permissionAdminService;

    public DefaultPermissionAdminServiceSearchTest() {
        super(DefaultPermissionAdminService.class, PermissionAdminService.class);
    }

    @Before
    public void setUp() throws Exception {
        setPageLimit(permissionAdminService, "maxGroups", 1000);
        setPageLimit(permissionAdminService, "maxUsers", 1000);

        when(authenticationContext.getCurrentUser()).thenReturn(TEST_USER);
    }

    @Test
    public void testFindGroupsWithGlobalPermission() {
        when(globalPermissionDao.findHighestPermissionPerGroup(isNull(String.class), any(PageRequest.class)))
                .thenAnswer(withLastPageOf(
                        new InternalPermittedGroup("one", SYS_ADMIN.getWeight()),
                        new InternalPermittedGroup("two", ADMIN.getWeight()),
                        new InternalPermittedGroup("three", LICENSED_USER.getWeight())
                ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithGlobalPermission(null, newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("one", SYS_ADMIN),
                permittedGroup("two", ADMIN),
                permittedGroup("three", LICENSED_USER)
        ));
    }

    @Test
    public void testFindGroupsWithGlobalPermissionUsingFilter() {
        when(globalPermissionDao.findHighestPermissionPerGroup(eq("foo"), any(PageRequest.class)))
                .thenAnswer(withLastPageOf(
                        new InternalPermittedGroup("foo", SYS_ADMIN.getWeight()),
                        new InternalPermittedGroup("foobar", ADMIN.getWeight()),
                        new InternalPermittedGroup("foobarbaz", LICENSED_USER.getWeight())
                ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithGlobalPermission("foo", newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("foo", SYS_ADMIN),
                permittedGroup("foobar", ADMIN),
                permittedGroup("foobarbaz", LICENSED_USER)
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithGlobalPermissionValidatesPermission() {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForGlobal(ADMIN);
        permissionAdminService.findGroupsWithGlobalPermission(null, newRequest(0, 10));
    }

    @Test
    public void testFindGroupsWithProjectPermission() throws Exception {
        when(projectPermissionDao.findHighestPermissionPerGroup(eq(TEST_PROJECT.getId()), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                new InternalPermittedGroup("one", PROJECT_ADMIN.getWeight()),
                new InternalPermittedGroup("two", PROJECT_WRITE.getWeight()),
                new InternalPermittedGroup("three", PROJECT_READ.getWeight())
        ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithProjectPermission(TEST_PROJECT, null,
                newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("one", PROJECT_ADMIN),
                permittedGroup("two", PROJECT_WRITE),
                permittedGroup("three", PROJECT_READ)
        ));
    }

    @Test
    public void testFindGroupsWithProjectPermissionUsingFilter() throws Exception {
        when(projectPermissionDao.findHighestPermissionPerGroup(eq(TEST_PROJECT.getId()), eq("foo"),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                new InternalPermittedGroup("foo", PROJECT_ADMIN.getWeight()),
                new InternalPermittedGroup("foobar", PROJECT_WRITE.getWeight()),
                new InternalPermittedGroup("foobarbaz", PROJECT_READ.getWeight())
        ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithProjectPermission(TEST_PROJECT, "foo",
                newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("foo", PROJECT_ADMIN),
                permittedGroup("foobar", PROJECT_WRITE),
                permittedGroup("foobarbaz", PROJECT_READ)
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithProjectPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService)
                .validateForProject(TEST_PROJECT, PROJECT_ADMIN);
        permissionAdminService.findGroupsWithProjectPermission(TEST_PROJECT, null, newRequest(0, 10));
    }

    @Test
    public void testFindGroupsWithRepositoryPermission() throws Exception {
        when(repositoryPermissionDao.findHighestPermissionPerGroup(eq(TEST_REPOSITORY.getId()), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                new InternalPermittedGroup("one", REPO_ADMIN.getWeight()),
                new InternalPermittedGroup("two", REPO_WRITE.getWeight()),
                new InternalPermittedGroup("three", REPO_READ.getWeight())
        ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithRepositoryPermission(TEST_REPOSITORY, null,
                newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("one", REPO_ADMIN),
                permittedGroup("two", REPO_WRITE),
                permittedGroup("three", REPO_READ)
        ));
    }

    @Test
    public void testFindGroupsWithRepositoryPermissionUsingFilter() throws Exception {
        when(repositoryPermissionDao.findHighestPermissionPerGroup(eq(TEST_REPOSITORY.getId()), eq("foo"),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                new InternalPermittedGroup("foo", REPO_ADMIN.getWeight()),
                new InternalPermittedGroup("foobar", REPO_WRITE.getWeight()),
                new InternalPermittedGroup("foobarbaz", REPO_READ.getWeight())
        ));

        Page<PermittedGroup> groups = permissionAdminService.findGroupsWithRepositoryPermission(TEST_REPOSITORY, "foo",
                newRequest(0, 10));

        assertThat(groups, isLastPageOf(
                permittedGroup("foo", REPO_ADMIN),
                permittedGroup("foobar", REPO_WRITE),
                permittedGroup("foobarbaz", REPO_READ)
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithRepositoryPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService)
                .validateForRepository(TEST_REPOSITORY, REPO_ADMIN);
        permissionAdminService.findGroupsWithRepositoryPermission(TEST_REPOSITORY, null, newRequest(0, 10));
    }

    @Test
    public void testFindGroupsWithoutGlobalPermission() throws Exception {
        when(globalPermissionDao.findGroupsWithPermission(any(PageRequest.class))).thenAnswer(withLastPageOf(
                "abc", "cde", "efg"));
        when(userService.findGroupsByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "abc", "bcd", "cde", "def", "efg"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutGlobalPermission(null, newRequest(0, 10));

        // filter out groups with granted permission
        assertThat(groups, isLastPageOf("bcd", "def"));
    }

    @Test
    public void testFindGroupsWithoutGlobalPermissionUsingFilter() throws Exception {
        // NOTE: the answer from both components must be ordered by group name...
        when(globalPermissionDao.findGroupsWithPermission(any(PageRequest.class))).thenAnswer(withLastPageOf(
                "bar", "baz", "foo"));
        when(userService.findGroupsByName(eq("ba"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "bad", "ban", "bar", "bat", "baz"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutGlobalPermission("ba", newRequest(0, 10));

        // filter out groups with granted permission
        assertThat(groups, isLastPageOf("bad", "ban", "bat"));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithoutGlobalPermissionValidatesPermission() {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForGlobal(ADMIN);
        permissionAdminService.findGroupsWithoutGlobalPermission(null, newRequest(0, 10));
    }

    @Test
    public void testFindGroupsWithoutProjectPermission() throws Exception {
        // NOTE: the answer from both components must be ordered by group name...
        when(projectPermissionDao.findGroupsWithPermission(eq(TEST_PROJECT.getId()), any(PageRequest.class)))
                .thenAnswer(withLastPageOf("abc", "cde", "efg"));
        when(userService.findGroupsByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "abc", "bcd", "cde", "def", "efg"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutProjectPermission(TEST_PROJECT, null,
                newRequest(0, 10));

        assertThat(groups, isLastPageOf("bcd", "def"));
    }

    @Test
    public void testFindGroupsWithoutProjectPermissionUsingFilter() throws Exception {
        // NOTE: the answer from both components must be ordered by group name...
        when(projectPermissionDao.findGroupsWithPermission(eq(TEST_PROJECT.getId()), any(PageRequest.class)))
                .thenAnswer(withLastPageOf("bar", "baz", "foo"));
        when(userService.findGroupsByName(eq("ba"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "bad", "ban", "bar", "bat", "baz"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutProjectPermission(TEST_PROJECT, "ba",
                newRequest(0, 10));

        // filter out groups with granted permission
        assertThat(groups, isLastPageOf("bad", "ban", "bat"));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithoutProjectPermissionValidatesPermission() {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForProject(TEST_PROJECT,
                PROJECT_ADMIN);
        permissionAdminService.findGroupsWithoutProjectPermission(TEST_PROJECT, null, newRequest(0, 10));
    }

    @Test
    public void testFindGroupsWithoutRepositoryPermission() throws Exception {
        // NOTE: the answer from both components must be ordered by group name...
        when(repositoryPermissionDao.findGroupsWithPermission(eq(TEST_REPOSITORY.getId()), any(PageRequest.class)))
                .thenAnswer(withLastPageOf("abc", "cde", "efg"));
        when(userService.findGroupsByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "abc", "bcd", "cde", "def", "efg"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutRepositoryPermission(TEST_REPOSITORY, null,
                newRequest(0, 10));

        assertThat(groups, isLastPageOf("bcd", "def"));
    }

    @Test
    public void testFindGroupsWithoutRepositoryPermissionUsingFilter() throws Exception {
        // NOTE: the answer from both components must be ordered by group name...
        when(repositoryPermissionDao.findGroupsWithPermission(eq(TEST_REPOSITORY.getId()), any(PageRequest.class)))
                .thenAnswer(withLastPageOf("bar", "baz", "foo"));
        when(userService.findGroupsByName(eq("ba"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                "bad", "ban", "bar", "bat", "baz"
        ));

        Page<String> groups = permissionAdminService.findGroupsWithoutRepositoryPermission(TEST_REPOSITORY, "ba",
                newRequest(0, 10));

        // filter out groups with granted permission
        assertThat(groups, isLastPageOf("bad", "ban", "bat"));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindGroupsWithoutRepositoryPermissionValidatesPermission() {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForRepository(TEST_REPOSITORY,
                REPO_ADMIN);
        permissionAdminService.findGroupsWithoutRepositoryPermission(TEST_REPOSITORY, null, newRequest(0, 10));
    }

    @Test
    public void testFindUsersWithGlobalPermission() throws Exception {
        when(globalPermissionDao.findHighestPermissionPerUser(eq(UserType.NORMAL), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithGlobalPermission(null, newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test
    public void testFindUsersWithGlobalPermissionUsingFilter() throws Exception {
        when(globalPermissionDao.findHighestPermissionPerUser(eq(UserType.NORMAL), eq("user"),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithGlobalPermission("user", newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindUsersWithGlobalPermissionValidatesPermission() {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForGlobal(ADMIN);
        permissionAdminService.findUsersWithGlobalPermission(null, newRequest(0, 10));
    }

    @Test
    public void testFindUsersWithProjectPermission() throws Exception {
        when(projectPermissionDao.findHighestPermissionPerUser(eq(TEST_PROJECT.getId()), eq(UserType.NORMAL),
                isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithProjectPermission(TEST_PROJECT, null,
                newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test
    public void testFindUsersWithProjectPermissionUsingFilter() throws Exception {
        when(projectPermissionDao.findHighestPermissionPerUser(eq(TEST_PROJECT.getId()), eq(UserType.NORMAL),
                eq("user"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithProjectPermission(TEST_PROJECT, "user",
                newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindUsersWithProjectPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForProject(TEST_PROJECT,
                PROJECT_ADMIN);
        permissionAdminService.findUsersWithProjectPermission(TEST_PROJECT, null, newRequest(0, 10));
    }

    @Test
    public void testFindUsersWithRepositoryPermission() throws Exception {
        when(repositoryPermissionDao.findHighestPermissionPerUser(eq(TEST_REPOSITORY.getId()), eq(UserType.NORMAL),
                isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithRepositoryPermission(TEST_REPOSITORY, null,
                newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test
    public void testFindUsersWithRepositoryPermissionUsingFilter() throws Exception {
        when(repositoryPermissionDao.findHighestPermissionPerUser(eq(TEST_REPOSITORY.getId()), eq(UserType.NORMAL),
                eq("user"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActivePermittedUser(1, SYS_ADMIN),
                createActivePermittedUser(2, ADMIN),
                createActivePermittedUser(3, LICENSED_USER),
                createPermittedUser(4, false, LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.findUsersWithRepositoryPermission(TEST_REPOSITORY, "user",
                newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER),
                permittedUser(4, LICENSED_USER) // inactive users should be included
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindUsersWithRepositoryPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForRepository(TEST_REPOSITORY,
                REPO_ADMIN);
        permissionAdminService.findUsersWithRepositoryPermission(TEST_REPOSITORY, null, newRequest(0, 10));
    }

    @Test
    public void testFindUsersWithoutGlobalPermission() throws Exception {
        when(globalPermissionDao.findUsersWithPermission(eq(UserType.NORMAL), any(PageRequest.class)))
                .thenAnswer(withLastPageOf(
                        createActiveUser(1),
                        createActiveUser(3),
                        createActiveUser(5)
                ));
        when(userService.findUsersByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(1),
                createActiveUser(2),
                createActiveUser(3),
                createActiveUser(4),
                createActiveUser(5),
                createActiveUser(6)
        ));


        Page<ApplicationUser> users = permissionAdminService.findUsersWithoutGlobalPermission(null, newRequest(0, 10));

        // filter out users with granted permission
        assertThat(users, isLastPageOf(
                userWithId(2),
                userWithId(4),
                userWithId(6)
        ));
    }

    @Test
    public void testFindUsersWithoutGlobalPermissionUsingFilter() throws Exception {
        // NOTE: the answer from both components must be ordered by username...
        when(globalPermissionDao.findUsersWithPermission(eq(UserType.NORMAL), any(PageRequest.class)))
                .thenAnswer(withLastPageOf(
                        createActiveUser(1, "bar"),
                        createActiveUser(2, "baz"),
                        createActiveUser(3, "foo")
                ));
        when(userService.findUsersByName(eq("ba"), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(4, "bad"),
                createActiveUser(5, "ban"),
                createActiveUser(1, "bar"),
                createActiveUser(6, "bat"),
                createActiveUser(2, "baz")
        ));


        Page<ApplicationUser> users = permissionAdminService.findUsersWithoutGlobalPermission("ba", newRequest(0, 10));

        // filter out users with granted permission
        assertThat(users, isLastPageOf(
                userWithName("bad"),
                userWithName("ban"),
                userWithName("bat")
        ));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindUsersWithoutGlobalPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForGlobal(ADMIN);
        permissionAdminService.findUsersWithoutGlobalPermission(null, newRequest(0, 10));
    }

    @Test
    public void testFindLicensedUsersWithoutProjectPermission() throws Exception {
        when(projectPermissionDao.findUsersWithPermission(eq(TEST_PROJECT.getId()), eq(UserType.NORMAL),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(1),
                createActiveUser(3),
                createActiveUser(5)
        ));
        when(userService.findUsersByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(1),
                createActiveUser(2),
                createActiveUser(3),
                createActiveUser(4),
                createActiveUser(5),
                createActiveUser(6)
        ));
        // stub licensed users, users not explicitly mentioned below will be unlicensed
        when(permissionService.hasGlobalPermission(argThat(userWithId(1)), eq(LICENSED_USER))).thenReturn(true);
        when(permissionService.hasGlobalPermission(argThat(userWithId(2)), eq(LICENSED_USER))).thenReturn(true);
        when(permissionService.hasGlobalPermission(argThat(userWithId(3)), eq(LICENSED_USER))).thenReturn(true);

        Page<ApplicationUser> users = permissionAdminService.findLicensedUsersWithoutProjectPermission(TEST_PROJECT, null,
                newRequest(0, 10));

        // filter out users with granted permission and licensed - only user "2"
        assertThat(users, isLastPageOf(userWithId(2)));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindLicensedUsersWithoutProjectPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForProject(TEST_PROJECT,
                PROJECT_ADMIN);
        permissionAdminService.findLicensedUsersWithoutProjectPermission(TEST_PROJECT, null, newRequest(0, 10));
    }

    @Test
    public void testFindLicensedUsersWithoutRepositoryPermission() throws Exception {
        when(repositoryPermissionDao.findUsersWithPermission(eq(TEST_REPOSITORY.getId()), eq(UserType.NORMAL),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(1),
                createActiveUser(3),
                createActiveUser(5)
        ));
        when(userService.findUsersByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createActiveUser(1),
                createActiveUser(2),
                createActiveUser(3),
                createActiveUser(4),
                createActiveUser(5),
                createActiveUser(6)
        ));
        // stub licensed users, users not explicitly mentioned below will be unlicensed
        when(permissionService.hasGlobalPermission(argThat(userWithId(1)), eq(LICENSED_USER))).thenReturn(true);
        when(permissionService.hasGlobalPermission(argThat(userWithId(2)), eq(LICENSED_USER))).thenReturn(true);
        when(permissionService.hasGlobalPermission(argThat(userWithId(3)), eq(LICENSED_USER))).thenReturn(true);

        Page<ApplicationUser> users = permissionAdminService.findLicensedUsersWithoutRepositoryPermission(TEST_REPOSITORY,
                null, newRequest(0, 10));

        // filter out users with granted permission and licensed - only user "2"
        assertThat(users, isLastPageOf(userWithId(2)));
    }

    @Test(expected = AuthorisationException.class)
    public void testFindLicensedUsersWithoutRepositoryPermissionValidatesPermission() throws Exception {
        doThrow(newAuthorisationException()).when(permissionValidationService).validateForRepository(TEST_REPOSITORY,
                REPO_ADMIN);
        permissionAdminService.findLicensedUsersWithoutRepositoryPermission(TEST_REPOSITORY, null, newRequest(0, 10));
    }

    @Test
    public void testSearchGroupsWithPermissionMaxPage() throws Exception {
        // reduce the limit for this test
        setPageLimit(permissionAdminService, "maxGroups", 100);

        when(globalPermissionDao.findHighestPermissionPerGroup(isNull(String.class), any(PageRequest.class)))
                .thenAnswer(withPagesUpTo(1000, PERMITTED_GROUP_GENERATOR));

        Page<PermittedGroup> groups = permissionAdminService.searchGroups(new PermittedGroupGlobalSearchRequest.Builder()
                .build(), newRequest(100, 200));

        assertThat(groups, not(isLastPage()));
        assertThat(groups, hasStart(100));
        assertThat(groups, hasSize(100)); // page has been reduced
    }

    @Test
    public void testSearchGroupsWithoutPermissionMaxPage() throws Exception {
        // reduce the limit for this test
        setPageLimit(permissionAdminService, "maxGroups", 100);

        when(globalPermissionDao.findGroupsWithPermission(any(PageRequest.class))).thenAnswer(
                withPagesUpTo(200, GROUP_GENERATOR));
        when(userService.findGroupsByName(isNull(String.class), any(PageRequest.class))).thenAnswer(
                withPagesUpTo(1000, GROUP_GENERATOR));

        Page<String> groups = permissionAdminService.searchGroupsLacking(new PermittedGroupGlobalSearchRequest.Builder()
                .build(), newRequest(100, 200));

        assertThat(groups, not(isLastPage()));
        assertThat(groups, hasStart(100));
        assertThat(groups, hasSize(100)); // page has been reduced
    }

    @Test
    public void testSearchServiceUsersWithPermission() {
        when(globalPermissionDao.findHighestPermissionPerUser(eq(UserType.SERVICE), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withLastPageOf(
                createPermittedUser(createServiceUser(1, "user A"), SYS_ADMIN),
                createPermittedUser(createServiceUser(2, "user B"), ADMIN),
                createPermittedUser(createServiceUser(3, "user C"), LICENSED_USER)
        ));

        Page<PermittedUser> users = permissionAdminService.searchUsers(new PermittedUserSearchRequest.Builder()
                .userType(UserType.SERVICE)
                .build(), newRequest(0, 10));

        assertThat(users, isLastPageOf(
                permittedUser(1, SYS_ADMIN),
                permittedUser(2, ADMIN),
                permittedUser(3, LICENSED_USER)
        ));
    }

    @SuppressWarnings("ConstantConditions")
    @Test
    public void testSearchUsersWithPermissionPaging() {
        when(globalPermissionDao.findHighestPermissionPerUser(eq(UserType.NORMAL), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withPagesUpTo(25, PERMITTED_USER_GENERATOR));

        Page<PermittedUser> users = permissionAdminService.searchUsers(new PermittedUserSearchRequest.Builder()
                .build(), newRequest(10, 10));

        assertThat(users, not(isLastPage()));
        assertThat(users, hasStart(10));
        assertThat(users, hasSize(10));

        users = permissionAdminService.searchUsers(new PermittedUserSearchRequest.Builder()
                .build(), users.getNextPageRequest());

        assertThat(users, isLastPage());
        assertThat(users, hasStart(20));
        assertThat(users, hasSize(5));
    }

    @Test
    public void testSearchUsersWithPermissionMaxPage() throws Exception {
        // reduce the limit for this test
        setPageLimit(permissionAdminService, "maxUsers", 100);

        when(globalPermissionDao.findHighestPermissionPerUser(eq(UserType.NORMAL), isNull(String.class),
                any(PageRequest.class))).thenAnswer(withPagesUpTo(1000, PERMITTED_USER_GENERATOR));

        Page<PermittedUser> users = permissionAdminService.searchUsers(new PermittedUserSearchRequest.Builder()
                .build(), newRequest(100, 200));

        assertThat(users, not(isLastPage()));
        assertThat(users, hasStart(100));
        assertThat(users, hasSize(100)); // page has been reduced
    }

    @Test
    public void testSearchServiceUsersWithoutPermission() {
        when(globalPermissionDao.findUsersWithPermission(eq(UserType.SERVICE), any(PageRequest.class)))
                .thenAnswer(withLastPageOf(
                        createServiceUser(1, "user A"),
                        createServiceUser(3, "user C"),
                        createServiceUser(5, "user E")
                ));
        when(userService.findServiceUsersByName(isNull(String.class), any(PageRequest.class))).thenAnswer(withLastPageOf(
                createServiceUser(1, "user A"),
                createServiceUser(2, "user B"),
                createServiceUser(3, "user C"),
                createServiceUser(4, "user D"),
                createServiceUser(5, "user E"),
                createServiceUser(6, "user F")
        ));

        Page<ApplicationUser> users = permissionAdminService.searchUsersLacking(new PermittedUserSearchRequest.Builder()
                .userType(UserType.SERVICE)
                .build(), newRequest(0, 10));

        // filter out users with granted permission
        assertThat(users, isLastPageOf(
                userWithId(2),
                userWithId(4),
                userWithId(6)
        ));
    }

    @Test
    public void testSearchUsersWithoutPermissionMaxPage() throws Exception {
        // reduce the limit for this test
        setPageLimit(permissionAdminService, "maxUsers", 100);

        when(globalPermissionDao.findUsersWithPermission(eq(UserType.NORMAL), any(PageRequest.class)))
                .thenAnswer(withPagesUpTo(200, ACTIVE_USER_GENERATOR));
        when(userService.findUsersByName(isNull(String.class), any(PageRequest.class)))
                .thenAnswer(withPagesUpTo(1000, ACTIVE_USER_GENERATOR));

        Page<ApplicationUser> users = permissionAdminService.searchUsersLacking(new PermittedUserSearchRequest.Builder()
                .build(), newRequest(100, 200));

        assertThat(users, not(isLastPage()));
        assertThat(users, hasStart(100));
        assertThat(users, hasSize(100)); // page has been reduced
    }

    @SuppressWarnings("ConstantConditions")
    @Test
    public void testSearchUsersWithoutPermissionPaging() {
        when(globalPermissionDao.findUsersWithPermission(eq(UserType.NORMAL), any(PageRequest.class)))
                .thenAnswer(withPagesUpTo(600, ACTIVE_USER_GENERATOR));
        when(userService.findUsersByName(isNull(String.class), any(PageRequest.class)))
                .thenAnswer(withPagesUpTo(650, ACTIVE_USER_GENERATOR));

        Page<ApplicationUser> users = permissionAdminService.searchUsersLacking(new PermittedUserSearchRequest.Builder()
                .build(), newRequest(600, 30));

        // should result in 50 users spread across 2 pages
        assertThat(users, not(isLastPage()));

        users = permissionAdminService.searchUsersLacking(new PermittedUserSearchRequest.Builder()
                .build(), users.getNextPageRequest());

        assertThat(users, isLastPage());
        assertThat(users, hasStart(630));
        assertThat(users, hasSize(20));
    }

    private static InternalPermittedUser createActivePermittedUser(int i, Permission permission) {
        return new InternalPermittedUser(createActiveUser(i), permission.getWeight());
    }

    private static InternalNormalUser createActiveUser(int id) {
        return createUser(id, true);
    }

    private static InternalNormalUser createActiveUser(int id, String name) {
        return createUser(id, name, true);
    }

    private static InternalPermittedUser createPermittedUser(int id, boolean active, Permission permission) {
        return createPermittedUser(createUser(id, active), permission);
    }

    private static InternalPermittedUser createPermittedUser(ApplicationUser user, Permission permission) {
        return new InternalPermittedUser(user, permission.getWeight());
    }

    private static InternalServiceUser createServiceUser(int id, String name) {
        return new InternalServiceUser.Builder()
                .id(id)
                .name(name)
                .displayName(name)
                .build();
    }

    private static InternalNormalUser createUser(int id, boolean active) {
        String name = "user" + StringUtils.leftPad(Integer.toString(id), 4, '0');
        return createUser(id, name, active);
    }

    private static InternalNormalUser createUser(int i, String username, boolean active) {
        return new InternalNormalUser.Builder()
                .id(i)
                .name(username)
                .crowdUser(new ImmutableUser(1, username, username, username + "@example.com", active))
                .build();
    }

    private static String groupNameFor(Integer id) {
        return "group" + StringUtils.leftPad(Integer.toString(id), 4, '0');
    }

    private static AuthorisationException newAuthorisationException() {
        return new AuthorisationException(new KeyedMessage("test", "Not authorised", "Not authorised"));
    }
}
