package com.atlassian.stash.internal.user;

import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.user.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.core.Authentication;

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

@RunWith(MockitoJUnitRunner.class)
public class ExtendedMethodSecurityExpressionRootTest {

    @Mock
    private Authentication authentication;
    private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();
    @Mock
    private PermissionService permissionService;
    @Mock
    private UserService userService;
    
    @Test(expected = IllegalArgumentException.class)
    public void testConstructorWithNullAuthentication() {
        //This is verifying a behaviour deep in the inheritance hierarchy, at the super-most class.
        new ExtendedMethodSecurityExpressionRoot(null, guard);
    }

    @Test
    public void testHasAnyPermission() {
        when(permissionService.hasAnyUserPermission(same(Permission.ADMIN))).thenReturn(false);
        when(permissionService.hasAnyUserPermission(same(Permission.REPO_READ))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        root.setPermissionService(permissionService);
        assertFalse(root.hasAnyPermission(Permission.ADMIN));
        assertTrue(root.hasAnyPermission(Permission.REPO_READ));
    }

    @Test
    public void testHasGlobalPermission() {
        when(permissionService.hasGlobalPermission(same(Permission.SYS_ADMIN))).thenReturn(true);
        when(permissionService.hasGlobalPermission(same(Permission.ADMIN))).thenReturn(false);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        root.setPermissionService(permissionService);
        assertTrue(root.hasGlobalPermission(Permission.SYS_ADMIN));
        assertFalse(root.hasGlobalPermission(Permission.ADMIN));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasGlobalWithProjectPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasGlobalPermission(Permission.PROJECT_READ);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasGlobalWithRepositoryPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasGlobalPermission(Permission.REPO_READ);
    }

    @Test
    public void testHasProjectPermission() {
        Project project = mock(Project.class);
        ApplicationUser user = mock(ApplicationUser.class);
        StashUserAuthenticationToken token = StashUserAuthenticationToken.forUser(user);

        when(permissionService.hasProjectPermission(same(project), same(Permission.PROJECT_READ))).thenReturn(true);
        when(permissionService.hasProjectPermission(eq(1), same(Permission.PROJECT_ADMIN))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(token, guard);
        root.setPermissionService(permissionService);
        assertTrue(root.hasProjectPermission(null, Permission.PROJECT_WRITE));
        assertTrue(root.hasProjectPermission(project, Permission.PROJECT_READ));
        assertTrue(root.hasProjectPermission(1, Permission.PROJECT_ADMIN));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasProjectPermissionWithGlobalPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasProjectPermission(null, Permission.ADMIN);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasProjectPermissionWithRepositoryPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasProjectPermission(null, Permission.REPO_ADMIN);
    }

    @Test
    public void testHasRepositoryPermission() throws Exception {
        Repository repository = mock(Repository.class);
        ApplicationUser user = mock(ApplicationUser.class);
        StashUserAuthenticationToken token = StashUserAuthenticationToken.forUser(user);

        when(permissionService.hasRepositoryPermission(same(repository), same(Permission.REPO_READ))).thenReturn(true);
        when(permissionService.hasRepositoryPermission(eq(1), same(Permission.REPO_ADMIN))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(token, guard);
        root.setPermissionService(permissionService);
        assertTrue(root.hasRepositoryPermission(null, Permission.REPO_WRITE));
        assertTrue(root.hasRepositoryPermission(repository, Permission.REPO_READ));
        assertTrue(root.hasRepositoryPermission(1, Permission.REPO_ADMIN));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasRepositoryPermissionWithGlobalPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasRepositoryPermission(null, Permission.ADMIN);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testHasRepositoryPermissionWithProjectPermission() {
        new ExtendedMethodSecurityExpressionRoot(authentication, guard).hasRepositoryPermission(null, Permission.PROJECT_ADMIN);
    }

    @Test
    public void testIsCurrentUserWithString() {
        StashUserAuthenticationToken token = mock(StashUserAuthenticationToken.class);
        when(token.getName()).thenReturn("bturner", "bturner", "jdoe");

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(token, guard);
        root.setPermissionService(permissionService);
        assertFalse(root.isCurrentUser((String) null)); //passed user is null
        assertTrue(root.isCurrentUser("bturner"));  //bturner == bturner
        assertFalse(root.isCurrentUser("bturner")); //bturner != jdoe
    }

    @Test
    public void testIsCurrentUserWithUser() {
        ApplicationUser user = mock(ApplicationUser.class);
        when(user.getName()).thenReturn("bturner");

        StashUserAuthenticationToken token = mock(StashUserAuthenticationToken.class);
        when(token.getName()).thenReturn("bturner", "jdoe");

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(token, guard);
        root.setPermissionService(permissionService);
        assertFalse(root.isCurrentUser((ApplicationUser) null)); //passed user is null
        assertTrue(root.isCurrentUser(user));  //bturner == bturner
        assertFalse(root.isCurrentUser(user)); //bturner != jdoe
    }

    @Test
    public void testIsProjectAccessible() throws Exception {
        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(mock(StashUserAuthenticationToken.class), guard);
        root.setPermissionService(permissionService);
        Project project = mock(Project.class);
        when(project.getId()).thenReturn(23);
        root.isProjectAccessible(project);
        root.isProjectAccessible(21);
        verify(permissionService).isProjectAccessible(project);
        verify(permissionService).isProjectAccessible(21);
        verifyNoMoreInteractions(permissionService);
    }

    @Test
    public void testIsRepositoryAccessible() throws Exception {
        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(mock(StashUserAuthenticationToken.class), guard);
        root.setPermissionService(permissionService);
        Repository repository = mock(Repository.class);
        when(repository.getId()).thenReturn(23);
        root.isRepositoryAccessible(repository);
        root.isRepositoryAccessible(21);
        verify(permissionService).isRepositoryAccessible(repository);
        verify(permissionService).isRepositoryAccessible(21);
        verifyNoMoreInteractions(permissionService);
    }

    @Test(expected = IllegalStateException.class)
    public void testPermissionCheckWithGuard() {
        guard.set(Boolean.TRUE);
        try {
            when(permissionService.hasGlobalPermission(same(Permission.ADMIN))).thenReturn(false);

            ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
            root.setPermissionService(permissionService);
            assertTrue(root.hasGlobalPermission(Permission.SYS_ADMIN));
            
        } finally {
            guard.remove();
        }
    }
    
    @Test
    public void testReturnObject() {
        Object returnObject = new Object();

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        assertNull(root.getReturnObject());

        root.setReturnObject(returnObject);
        assertSame(returnObject, root.getReturnObject());

        root.setReturnObject(null);
        assertNull(root.getReturnObject());
    }

    @Test
    public void testUserHasGlobalPermission() throws Exception {
        ApplicationUser user = new InternalNormalUser.Builder().name("test").build();
        when(permissionService.hasGlobalPermission(eq(user), same(Permission.SYS_ADMIN))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        root.setPermissionService(permissionService);
        assertTrue(root.hasGlobalPermission(user, Permission.SYS_ADMIN));
        assertFalse(root.hasGlobalPermission(user, Permission.ADMIN));
    }

    @Test
    public void testUserHasGlobalPermissionByName() throws Exception {
        String name = "test";
        ApplicationUser user = new InternalNormalUser.Builder().name(name).build();
        when(userService.getUserByName(name)).thenReturn(user);
        when(permissionService.hasGlobalPermission(eq(name), same(Permission.SYS_ADMIN))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        root.setPermissionService(permissionService);
        assertTrue(root.hasGlobalPermission(name, Permission.SYS_ADMIN));
        assertFalse(root.hasGlobalPermission(name, Permission.ADMIN));
    }

    @Test
    public void testUserHasGlobalPermissionForUnknown() throws Exception {
        when(permissionService.hasGlobalPermission(same(Permission.SYS_ADMIN))).thenReturn(true);

        ExtendedMethodSecurityExpressionRoot root = new ExtendedMethodSecurityExpressionRoot(authentication, guard);
        root.setPermissionService(permissionService);
        assertFalse(root.hasGlobalPermission("unknown", Permission.SYS_ADMIN));
        assertFalse(root.hasGlobalPermission("unknown", Permission.ADMIN));
    }
}
