package com.atlassian.stash.internal.user;

import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.annotation.DataSet;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermittedGroup;
import com.atlassian.bitbucket.permission.PermittedUser;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserType;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.atlassian.stash.internal.DataSets;
import com.google.common.collect.ImmutableList;
import org.junit.Test;

import java.util.List;

import static com.atlassian.bitbucket.hamcrest.StashUserMatchers.stashUser;
import static com.atlassian.stash.internal.user.GrantedPermissionMatcher.groupPermission;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;

@DataSet({DataSets.USERS,
          DataSets.GLOBAL_PERMISSIONS})
public class HibernateGlobalPermissionDaoTest extends AbstractPermissionDaoTest {

    @Dao
    private HibernateGlobalPermissionDao dao;
    @Dao(HibernateStashUserDao.class)
    private StashUserDao userDao;

    @Test
    public void testCreate() throws Exception {
        InternalApplicationUser user = userDao.getById(4);

        InternalGlobalPermission permission = new InternalGlobalPermission.Builder()
                .group("trendygroup")
                .permission(Permission.ADMIN)
                .user(user)
                .build();
        InternalGrantedPermission created = dao.create(permission);

        flush();
        clear();

        assertNotNull(permission.getId());
        assertEquals(permission.getId(), created.getId());
        assertEquals(permission, dao.getById(created.getId()));
    }


    @Test
    public void testDelete() {
        long permId = 15L;
        InternalGlobalPermission permission = dao.getById(permId);
        assertThat(permission, groupPermission(Permission.LICENSED_USER, "licensed_group"));
        dao.delete(permission);

        flush();
        clear();

        assertNull(dao.getById(permId));
    }

    @Test
    public void testDeleteWithRevoke() {
        long permId = 15L;
        InternalGlobalPermission permission = dao.getById(permId);
        assertThat(permission, groupPermission(Permission.LICENSED_USER, "licensed_group"));
        dao.revoke(permission);

        flush();
        clear();

        assertNull(dao.getById(permId));
    }

    @Test
    public void testRevokeAllForGroup() throws Exception {
        final String groupName = "stash-admins";

        // find the number of explicit permissions granted to the group
        int current = countAllPermissionsFor(groupName);
        assertThat(current, greaterThan(0));

        // revoke them  all
        assertEquals(current, dao.revokeAll(groupName));

        flush();
        clear();

        // check the deletion was effective
        int remaining = countAllPermissionsFor(groupName);
        assertEquals(0, remaining);
    }

    @Test
    public void testRevokeAllForUser() throws Exception {
        final int userId = 2;

        // find the number of explicit permissions granted to the user
        int current = countAllPermissionsFor(userId);
        assertThat(current, greaterThan(0));

        // revoke them  all
        assertEquals(current, dao.revokeAll(userId));

        flush();
        clear();

        // check the deletion was effective
        int remaining = countAllPermissionsFor(userId);
        assertEquals(0, remaining);
    }

    @Test
    public void testFindUsersWithPermission() {
        Page<ApplicationUser> users = dao.findUsersWithPermission(UserType.NORMAL, new PageRequestImpl(0, 10));
        assertEquals(3, users.getSize());

        List<ApplicationUser> values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0), stashUser(2, "admin"));
        assertThat(values.get(1), stashUser(1, "sysadmin"));
        assertThat(values.get(2), stashUser(9, "xlicensed"));
    }

    @Test
    public void testFindServiceUsersWithPermission() {
        Page<ApplicationUser> users = dao.findUsersWithPermission(UserType.SERVICE, new PageRequestImpl(0, 10));
        assertEquals(2, users.getSize());

        List<ApplicationUser> values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0), stashUser(13, "access:1"));
        assertThat(values.get(1), stashUser(14, "optimus.prime"));
    }

    @Test
    public void testFindHighestPermissionPerUser() throws Exception {
        Page<PermittedUser> users = dao.findHighestPermissionPerUser(UserType.NORMAL, null, new PageRequestImpl(0, 10));
        assertEquals(3, users.getSize());

        List<PermittedUser> values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
        assertThat(values.get(0).getUser(), stashUser(2, "admin"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(1).getUser(), stashUser(1, "sysadmin"));
        assertThat(values.get(2).getPermission(), equalTo(Permission.LICENSED_USER));
        assertThat(values.get(2).getUser(), stashUser(9, "xlicensed"));

        // Empty string should be handled the same way
        users = dao.findHighestPermissionPerUser(UserType.NORMAL, "", new PageRequestImpl(0, 10));
        assertEquals(3, users.getSize());

        values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
        assertThat(values.get(0).getUser(), stashUser(2, "admin"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(1).getUser(), stashUser(1, "sysadmin"));
        assertThat(values.get(2).getPermission(), equalTo(Permission.LICENSED_USER));
        assertThat(values.get(2).getUser(), stashUser(9, "xlicensed"));
    }

    @Test
    public void testFindHighestPermissionPerServiceUser() throws Exception {
        Page<PermittedUser> users = dao.findHighestPermissionPerUser(UserType.SERVICE, null, new PageRequestImpl(0, 10));
        assertEquals(2, users.getSize());

        List<PermittedUser> values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
        assertThat(values.get(0).getUser(), stashUser(13, "access:1"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(1).getUser(), stashUser(14, "optimus.prime"));

        // Empty string should be handled the same way
        users = dao.findHighestPermissionPerUser(UserType.SERVICE, "", new PageRequestImpl(0, 10));
        assertEquals(2, users.getSize());

        values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
        assertThat(values.get(0).getUser(), stashUser(13, "access:1"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(1).getUser(), stashUser(14, "optimus.prime"));
    }

    @Test
    public void testFindHighestPermissionPerUserWithFilter() throws Exception {
        Page<PermittedUser> users = dao.findHighestPermissionPerUser(UserType.NORMAL, "admin", new PageRequestImpl(0, 10));
        assertEquals(2, users.getSize());

        List<PermittedUser> values = ImmutableList.copyOf(users.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
        assertThat(values.get(0).getUser(), stashUser(2, "admin"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(1).getUser(), stashUser(1, "sysadmin"));
    }

    @Test
    public void testFindHighestPermissionPerUserWithFilterAndMoreVariations() throws Exception {
        String[] filters = {
                "xli", // filter should match the start of the username
                "sed", // filter should match the end of the username
                "cen", // filter should match within the username
                "CEN", // filter should match within the username (case insensitivity)
                "CeN"  // filter should match within the username (case insensitivity)
        };
        for (String filter : filters) {
            Page<PermittedUser> users = dao.findHighestPermissionPerUser(UserType.NORMAL, filter, new PageRequestImpl(0, 10));
            assertEquals(1, users.getSize());

            List<PermittedUser> values = ImmutableList.copyOf(users.getValues());
            assertThat(values.get(0).getPermission(), equalTo(Permission.LICENSED_USER));
            assertThat(values.get(0).getUser(), stashUser(9, "xlicensed"));
        }
    }

    @Test
    public void testFindHighestPermissionPerServiceUserWithFilterAndMoreVariations() throws Exception {
        String[] filters = {
                "access", // filter should match the start of the display name
                ".1",     // filter should match the end of the display name
                ".key.",  // filter should match within the display name
                ".KEY.",  // filter should match within the display name (case insensitivity)
                ".kEy."   // filter should match within the display name (case insensitivity)
        };
        for (String filter : filters) {
            Page<PermittedUser> users = dao.findHighestPermissionPerUser(UserType.SERVICE, filter, new PageRequestImpl(0, 10));
            assertEquals(1, users.getSize());

            List<PermittedUser> values = ImmutableList.copyOf(users.getValues());
            assertThat(values.get(0).getPermission(), equalTo(Permission.ADMIN));
            assertThat(values.get(0).getUser(), stashUser(13, "access:1"));
        }
    }

    @Test
    public void testFindGroupsWithPermission() throws Exception {
        Page<String> groups = dao.findGroupsWithPermission(new PageRequestImpl(0, 10));
        assertEquals(4, groups.getSize());

        List<String> values = ImmutableList.copyOf(groups.getValues());
        assertEquals("group-with-global-and-resource-perms", values.get(0));
        assertEquals("licensed_group", values.get(1));
        assertEquals("stash-admins", values.get(2));
        assertEquals("stash-users", values.get(3));
    }

    @Test
    public void testFindHighestPermissionPerGroup() throws Exception {
        Page<PermittedGroup> groups = dao.findHighestPermissionPerGroup(null, new PageRequestImpl(0, 10));
        assertEquals(4, groups.getSize());

        List<PermittedGroup> values = ImmutableList.copyOf(groups.getValues());
        assertThat(values.get(0).getPermission(), equalTo(Permission.LICENSED_USER));
        assertThat(values.get(0).getGroup(), equalTo("group-with-global-and-resource-perms"));
        assertThat(values.get(1).getPermission(), equalTo(Permission.LICENSED_USER));
        assertThat(values.get(1).getGroup(), equalTo("licensed_group"));
        assertThat(values.get(2).getPermission(), equalTo(Permission.SYS_ADMIN));
        assertThat(values.get(2).getGroup(), equalTo("stash-admins"));
        assertThat(values.get(3).getPermission(), equalTo(Permission.LICENSED_USER));
        assertThat(values.get(3).getGroup(), equalTo("stash-users"));
    }

    @Test
    public void testFindHighestPermissionPerGroupWithFilter() throws Exception {
        for (String group : new String[] {"ER", "er", "eR"}) { // verify case insensitivity
            Page<PermittedGroup> groups = dao.findHighestPermissionPerGroup(group, new PageRequestImpl(0, 10));
            assertEquals(2, groups.getSize());

            List<PermittedGroup> values = ImmutableList.copyOf(groups.getValues());
            assertThat(values.get(0).getPermission(), equalTo(Permission.LICENSED_USER));
            assertThat(values.get(0).getGroup(), equalTo("group-with-global-and-resource-perms"));
            assertThat(values.get(1).getPermission(), equalTo(Permission.LICENSED_USER));
            assertThat(values.get(1).getGroup(), equalTo("stash-users"));
        }
    }

    @Test
    public void testFindHighestPermissionPerGroupWithFilterAndMoreVariations() throws Exception {
        String[] filters = {
                "group-with", // filter should match the start of the group name
                "perms", // filter should match the end of the group name
                "global-and-resource", // filter should match within the group name
                "WITH", // filter should match within the group name (case insensitivity)
                "WiTH"  // filter should match within the group name (case insensitivity)
        };
        for (String filter : filters) {
            Page<PermittedGroup> groups = dao.findHighestPermissionPerGroup(filter, new PageRequestImpl(0, 10));
            assertEquals(1, groups.getSize());

            List<PermittedGroup> values = ImmutableList.copyOf(groups.getValues());
            assertThat(values.get(0).getPermission(), equalTo(Permission.LICENSED_USER));
            assertThat(values.get(0).getGroup(), equalTo("group-with-global-and-resource-perms"));
        }
    }

    @Test
    public void testHasUserPermission() throws Exception {
        InternalApplicationUser user = userDao.findByName("xlicensed");
        assertFalse(dao.hasPermissionEntry(new InternalGlobalPermission.Builder()
                .permission(Permission.ADMIN)
                .user(user)
                .build()
        ));
        assertTrue(dao.hasPermissionEntry(new InternalGlobalPermission.Builder()
                .permission(Permission.LICENSED_USER)
                .user(user)
                .build()
        ));
    }

    @Test
    public void testHasGroupPermission() throws Exception {
        assertFalse(dao.hasPermissionEntry(new InternalGlobalPermission.Builder()
                .permission(Permission.ADMIN)
                .group("licensed_group")
                .build()
        ));
        assertTrue(dao.hasPermissionEntry(new InternalGlobalPermission.Builder()
                .permission(Permission.LICENSED_USER)
                .group("licensed_group")
                .build()
        ));
    }

    private int countAllPermissionsFor(String groupName) {
        return dao.findAll(new PageRequestImpl(0, 50), groupNameEquals(groupName)).getSize();
    }

    private int countAllPermissionsFor(int userId) {
        return dao.findAll(new PageRequestImpl(0, 50), userIdEquals(userId)).getSize();
    }

}
