package com.atlassian.crowd.dao.user;

import com.atlassian.crowd.dao.AbstractCrowdDaoTest;
import com.atlassian.crowd.dao.CrowdModelFactory;
import com.atlassian.crowd.embedded.api.PasswordCredential;
import com.atlassian.crowd.exception.UserNotFoundException;
import com.atlassian.crowd.model.user.InternalUser;
import com.atlassian.crowd.model.user.InternalUserAttribute;
import com.atlassian.crowd.model.user.User;
import com.atlassian.crowd.search.builder.Restriction;
import com.atlassian.crowd.search.query.entity.UserQuery;
import com.atlassian.crowd.search.query.entity.restriction.constants.UserTermKeys;
import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.annotation.DataSet;
import com.atlassian.stash.internal.DataSets;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.junit.Test;

import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;


@DataSet({DataSets.CWD_DIRECTORIES, DataSets.CWD_USERS})
public class UserDAOHibernateTest extends AbstractCrowdDaoTest {

    public static final String NAME_ACTIVE_USER = "active_cwd_user";
    public static final String NAME_INACTIVE_USER = "inactive_cwd_user";
    public static final String NAME_NEW_USER = "newuser";

    @Dao
    private UserDAOHibernate dao;

    @Test
    public void testAttributes() throws Exception {
        InternalUser user = dao.add(CrowdModelFactory.newUser(NAME_NEW_USER, "first", "last", true, DEFAULT_DIR_ID), new PasswordCredential("encrypted password", true));
        dao.storeAttributes(user, ImmutableMap.of("key", (Set<String>) ImmutableSet.of("value1", "value2")));

        flush();
        evict(user, user.getId());
        user = dao.findByName(DEFAULT_DIR_ID, NAME_NEW_USER);

        Set<InternalUserAttribute> userAttributes = dao.findUserAttributes(user.getId());
        assertEquals(2, userAttributes.size());
        assertHasNameValue(userAttributes, "key", "value1");
        assertHasNameValue(userAttributes, "key", "value2");

        dao.removeAttribute(user, "key");
        flush();
        evict(user, user.getId());
        user = dao.findByName(DEFAULT_DIR_ID, NAME_NEW_USER);

        userAttributes = dao.findUserAttributes(user.getId());
        assertEquals(0, userAttributes.size());
    }

    @Test
    public void testCreate() throws Exception {
        User added = CrowdModelFactory.newUser(NAME_NEW_USER, "first", "last", true, DEFAULT_DIR_ID);
        InternalUser created = dao.add(added, new PasswordCredential("encrypted password", true));

        assertNotNull(created.getId());
        assertTrue(created.isActive());

        flush();
        evict(created, created.getId());

        created = dao.findByName(DEFAULT_DIR_ID, NAME_NEW_USER);
        assertEquals(added.getFirstName(), created.getFirstName());
        assertEquals(added.getLastName(), created.getLastName());
    }

    @Test(expected = UserNotFoundException.class)
    public void testRemove() throws Exception {
        InternalUser user = null;
        try {
            user = dao.findByName(DEFAULT_DIR_ID, NAME_ACTIVE_USER);
        } catch (UserNotFoundException e) {
            fail("user active_cwd_user should exist at the start of the test");
        }

        assertNotNull(user);
        dao.remove(user);

        flush();
        evict(user, user.getId());

        dao.findByName(DEFAULT_DIR_ID, NAME_ACTIVE_USER);
    }

    @Test(expected = UserNotFoundException.class)
    public void testRemoveAll() throws Exception {
        dao.removeAll(DEFAULT_DIR_ID);

        flush();

        dao.findByName(DEFAULT_DIR_ID, NAME_ACTIVE_USER);
    }

    @Test
    public void testSearch() throws Exception {
        List<User> activeUsers = dao.search(DEFAULT_DIR_ID, new UserQuery<>(User.class, Restriction.on(UserTermKeys.ACTIVE).exactlyMatching(true), 0, Integer.MAX_VALUE));

        assertEquals(1, activeUsers.size());
        assertEquals(NAME_ACTIVE_USER, activeUsers.get(0).getName());

        List<User> inactiveUsers = dao.search(DEFAULT_DIR_ID, new UserQuery<>(User.class, Restriction.on(UserTermKeys.ACTIVE).exactlyMatching(false), 0, Integer.MAX_VALUE));

        assertEquals(1, inactiveUsers.size());
        assertEquals(NAME_INACTIVE_USER, inactiveUsers.get(0).getName());
    }

    @Test
    public void testUpdate() throws Exception {
        InternalUser user = dao.findByName(DEFAULT_DIR_ID, NAME_ACTIVE_USER);

        user.updateDetailsFrom(CrowdModelFactory.newUser(NAME_ACTIVE_USER, "new first", "new last", false, DEFAULT_DIR_ID));
        dao.update(user);

        flush();
        evict(user, user.getId());

        user = dao.findByName(DEFAULT_DIR_ID, NAME_ACTIVE_USER);
        assertNotNull(user);
        assertFalse(user.isActive());
        assertEquals("new first", user.getFirstName());
        assertEquals("new last", user.getLastName());
    }

    private void assertHasNameValue(Set<InternalUserAttribute> userAttributes, final String name, final String value) {
        assertTrue(Iterables.any(userAttributes, new Predicate<InternalUserAttribute>() {

            @Override
            public boolean apply(@Nullable InternalUserAttribute attribute) {
                return attribute.getName().equals(name) && attribute.getValue().equals(value);
            }
        }));
    }
}
