/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.uberfire.ext.security.management.tomcat;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.catalina.users.MemoryUserDatabase;
import org.apache.commons.io.FileUtils;
import org.jboss.errai.security.shared.api.Group;
import org.jboss.errai.security.shared.api.Role;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.errai.security.shared.api.identity.UserImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.uberfire.backend.server.security.RoleRegistry;
import org.uberfire.commons.config.ConfigProperties;
import org.uberfire.ext.security.management.BaseTest;
import org.uberfire.ext.security.management.api.AbstractEntityManager;
import org.uberfire.ext.security.management.api.Capability;
import org.uberfire.ext.security.management.api.CapabilityStatus;
import org.uberfire.ext.security.management.api.UserManager;
import org.uberfire.ext.security.management.api.exception.UserNotFoundException;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * This tests create temporary working copy of the "tomcat-users.xml" file as the tests are run using the real tomcat admin api for realm management.
 */
@RunWith(MockitoJUnitRunner.class)
public class TomcatUserManagerTest extends BaseTest {

    protected static final String ADMIN = "admin";
    protected static final String USERS_FILE_PATH = "org/uberfire/ext/security/management/tomcat/";
    protected static final String USERS_FILE_NAME = "tomcat-users.xml";

    @ClassRule
    public static TemporaryFolder tempFolder = new TemporaryFolder();

    private static File elHome;

    @Spy
    private TomcatUserManager usersManager = new TomcatUserManager();

    @BeforeClass
    public static void initWorkspace() throws Exception {
        elHome = tempFolder.newFolder("uf-extensions-security-management-tomcat");
        RoleRegistry.get().clear();
    }

    @Before
    public void setup() throws Exception {
        URL templateURL = Thread.currentThread().getContextClassLoader().getResource(USERS_FILE_PATH + USERS_FILE_NAME);
        File templateFile = new File(templateURL.getFile());
        FileUtils.cleanDirectory(elHome);
        FileUtils.copyFileToDirectory(templateFile,
                                      elHome);
        String full = new File(elHome,
                               templateFile.getName()).getAbsolutePath();
        String path = full.substring(0,
                                     full.lastIndexOf(File.separator));
        String name = full.substring(full.lastIndexOf(File.separator) + 1,
                                     full.length());
        Map<String, String> props = new HashMap<String, String>(1);
        props.put("org.uberfire.ext.security.management.tomcat.catalina-base",
                  path);
        props.put("org.uberfire.ext.security.management.tomcat.users-file",
                  name);
        System.setProperty(BaseTomcatManager.CATALINA_BASE_PROPERTY,
                           "");
        usersManager.loadConfig(new ConfigProperties(props));
        usersManager.initialize(userSystemManager);
    }

    @After
    public void finishIt() throws Exception {
        usersManager.destroy();
    }

    @Test
    public void testCapabilities() {
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_SEARCH_USERS),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_READ_USER),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_UPDATE_USER),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_ADD_USER),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_DELETE_USER),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_MANAGE_ATTRIBUTES),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_ASSIGN_GROUPS),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_CHANGE_PASSWORD),
                     CapabilityStatus.ENABLED);
        assertEquals(usersManager.getCapabilityStatus(Capability.CAN_ASSIGN_ROLES),
                     CapabilityStatus.ENABLED);
    }

    @Test
    public void testAttributes() {
        final Collection<UserManager.UserAttribute> USER_ATTRIBUTES = Arrays.asList(BaseTomcatManager.USER_FULLNAME);
        Collection<UserManager.UserAttribute> attributes = usersManager.getSettings().getSupportedAttributes();
        assertEquals(attributes,
                     USER_ATTRIBUTES);
    }

    @Test(expected = RuntimeException.class)
    public void testSearchPageZero() {
        AbstractEntityManager.SearchRequest request = buildSearchRequestMock("",
                                                                             0,
                                                                             5);
        AbstractEntityManager.SearchResponse<User> response = usersManager.search(request);
    }

    @Test
    public void testSearchAll() {
        AbstractEntityManager.SearchRequest request = buildSearchRequestMock("",
                                                                             1,
                                                                             5);
        AbstractEntityManager.SearchResponse<User> response = usersManager.search(request);
        assertNotNull(response);
        List<User> users = response.getResults();
        int total = response.getTotal();
        boolean hasNextPage = response.hasNextPage();
        assertEquals(total,
                     4);
        assertTrue(!hasNextPage);
        assertEquals(users.size(),
                     4);
        Set<User> expectedUsers = new HashSet<User>(4);
        expectedUsers.add(create(ADMIN));
        expectedUsers.add(create("user1"));
        expectedUsers.add(create("user2"));
        expectedUsers.add(create("user3"));
        assertThat(new HashSet<User>(users),
                   is(expectedUsers));
    }

    @Test
    public void testGetAdmin() {
        User user = usersManager.get(ADMIN);
        assertUser(user,
                   ADMIN);
    }

    @Test
    public void testGetUser1() {
        User user = usersManager.get("user1");
        assertUser(user,
                   "user1");
    }

    @Test
    public void testGetUser2() {
        User user = usersManager.get("user2");
        assertUser(user,
                   "user2");
    }

    @Test
    public void testGetUser3() {
        User user = usersManager.get("user3");
        assertUser(user,
                   "user3");
    }

    @Test
    public void testCreateUser() {
        User user = mock(User.class);
        when(user.getIdentifier()).thenReturn("user4");
        User userCreated = usersManager.create(user);
        assertUser(userCreated,
                   "user4");
    }

    @Test
    public void testUpdateUser() {
        User user = mock(User.class);
        when(user.getIdentifier()).thenReturn("user1");
        Map<String, String> properties = new HashMap<String, String>(1);
        properties.put(BaseTomcatManager.ATTRIBUTE_USER_FULLNAME,
                       "user1 Full Name");
        when(user.getProperty(BaseTomcatManager.ATTRIBUTE_USER_FULLNAME)).thenReturn("user1 Full Name");
        when(user.getProperties()).thenReturn(properties);
        User userUpdated = usersManager.update(user);
        assertNotNull(userUpdated);
        assertEquals("user1 Full Name",
                     userUpdated.getProperty(BaseTomcatManager.ATTRIBUTE_USER_FULLNAME));
    }

    @Test(expected = UserNotFoundException.class)
    public void testDeleteUser() {
        usersManager.delete("user1");
        usersManager.get("user1");
    }

    @Test
    public void testAssignGroups() {
        final User user = mock(User.class);
        when(user.getIdentifier()).thenReturn("user1");
        when(user.getRoles()).thenReturn(new HashSet<Role>());
        UserManager userManagerMock = mock(UserManager.class);
        doAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocationOnMock) throws Throwable {
                return user;
            }
        }).when(userManagerMock).get("user1");
        when(userSystemManager.users()).thenReturn(userManagerMock);
        Collection<String> groups = new ArrayList<String>();
        groups.add("role1");
        groups.add("role3");
        usersManager.assignGroups("user1",
                                  groups);
        Set<Group> result = usersManager.get("user1").getGroups();
        assertNotNull(result);
        assertEquals(2,
                     result.size());
    }

    @Test
    public void testAssignRoles() {
        RoleRegistry.get().registerRole("role1");
        RoleRegistry.get().registerRole("role3");
        final User user = mock(User.class);
        when(user.getIdentifier()).thenReturn("user1");
        when(user.getGroups()).thenReturn(new HashSet<Group>());
        UserManager userManagerMock = mock(UserManager.class);
        doAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocationOnMock) throws Throwable {
                return user;
            }
        }).when(userManagerMock).get("user1");
        when(userSystemManager.users()).thenReturn(userManagerMock);
        Collection<String> roles = new ArrayList<String>();
        roles.add("role1");
        roles.add("role3");
        usersManager.assignRoles("user1",
                                 roles);
        Set<Role> result = usersManager.get("user1").getRoles();
        assertNotNull(result);
        assertEquals(2,
                     result.size());
    }

    // Note that role3 cannot be assigned as it's not registered in the Roles Registry.
    @Test
    public void testAssignRolesNotAllRegistered() {
        RoleRegistry.get().registerRole("role1");
        final User user = mock(User.class);
        when(user.getIdentifier()).thenReturn("user1");
        when(user.getGroups()).thenReturn(new HashSet<Group>());
        UserManager userManagerMock = mock(UserManager.class);
        doAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocationOnMock) throws Throwable {
                return user;
            }
        }).when(userManagerMock).get("user1");
        when(userSystemManager.users()).thenReturn(userManagerMock);
        Collection<String> roles = new ArrayList<String>();
        roles.add("role1");
        roles.add("role3");
        usersManager.assignRoles("user1",
                                 roles);
        Set<Role> result = usersManager.get("user1").getRoles();
        assertNotNull(result);
        assertEquals(1,
                     result.size());
    }

    @Test
    public void testChangePassword() {
        usersManager.changePassword("user1",
                                    "newUser1Password");
        MemoryUserDatabase database = usersManager.getDatabase();
        org.apache.catalina.User catalinaUser = usersManager.getUser(database,
                                                                     "user1");
        assertEquals("newUser1Password",
                     catalinaUser.getPassword());
    }

    private User create(String username) {
        return new UserImpl(username);
    }

    private void assertUser(User user,
                            String username) {
        assertNotNull(user);
        assertEquals(user.getIdentifier(),
                     username);
    }
}
