package com.atlassian.stash.internal.crowd;

import com.atlassian.crowd.embedded.api.ApplicationFactory;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.exception.UserAlreadyExistsException;
import com.atlassian.crowd.model.application.Application;
import com.atlassian.crowd.model.application.DirectoryMapping;
import com.atlassian.crowd.model.user.User;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.sal.api.usersettings.UserSettings;
import com.atlassian.sal.api.usersettings.UserSettingsBuilder;
import com.atlassian.stash.internal.project.InternalPersonalProject;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.project.ProjectDao;
import com.atlassian.stash.internal.user.InternalNormalUser;
import com.atlassian.stash.internal.user.StashUserDao;
import com.atlassian.bitbucket.mockito.MockitoUtils;
import com.google.common.base.Function;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.transaction.PlatformTransactionManager;

import java.util.Arrays;

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

@RunWith(MockitoJUnitRunner.class)
public class HibernateUserDaoTest {
    private static final long HIGH_DIRECTORY_ID = 9001L;
    private static final long DIRECTORY_ID = 42L;
    private static final long LOW_DIRECTORY_ID = 99L;
    private static final int USER_ID = 9001;
    private static final String USERNAME = "donkey_kong";

    private InternalNormalUser stashUser = new InternalNormalUser.Builder().id(USER_ID).name(USERNAME).build();

    @Mock
    private PlatformTransactionManager txManager;
    @Mock
    private StashUserDao stashUserDao;
    @Mock
    private ProjectDao projectDao;
    @Mock
    private EventPublisher eventPublisher;
    @Mock
    private ApplicationFactory applicationFactory;
    @Mock
    Application application;

    @Mock
    private User crowdUser;
    @Captor
    private ArgumentCaptor<Function<UserSettingsBuilder, UserSettings>> updateFunctionCaptor;

    private HibernateUserDao dao;

    @Before
    public void setUp() {
        dao = new HibernateUserDao(eventPublisher, projectDao, stashUserDao, txManager);
        dao.setApplicationFactory(applicationFactory);

        when(crowdUser.getName()).thenReturn(USERNAME);
        when(crowdUser.getDirectoryId()).thenReturn(DIRECTORY_ID);

        when(applicationFactory.getApplication()).thenReturn(application);
        DirectoryMapping mapping1 = mock(DirectoryMapping.class);
        Directory directory1 = mock(Directory.class);
        when(directory1.getId()).thenReturn(HIGH_DIRECTORY_ID);
        when(mapping1.getDirectory()).thenReturn(directory1);
        DirectoryMapping mapping2 = mock(DirectoryMapping.class);
        Directory directory2 = mock(Directory.class);
        when(directory2.getId()).thenReturn(DIRECTORY_ID);
        when(mapping2.getDirectory()).thenReturn(directory2);
        DirectoryMapping mapping3 = mock(DirectoryMapping.class);
        Directory directory3 = mock(Directory.class);
        when(directory3.getId()).thenReturn(LOW_DIRECTORY_ID);
        when(mapping3.getDirectory()).thenReturn(directory3);
        when(application.getDirectoryMappings()).thenReturn(Arrays.asList(mapping1, mapping2, mapping3));

        when(projectDao.getByOwner(eq(USER_ID))).thenReturn(new InternalPersonalProject.Builder().owner(stashUser).build());
        when(projectDao.update(any(InternalProject.class))).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return invocation.getArguments()[0];
            }
        });
    }

    @Test
    public void testRenamePersonalProject() throws Exception {
        String newName = USERNAME + "-new";
        dao.renamePersonalProject(stashUser.copy().name(newName).build());

        ArgumentCaptor<InternalProject> captor = ArgumentCaptor.forClass(InternalProject.class);
        verify(projectDao).update(captor.capture());
        InternalProject renamed = captor.getValue();
        assertNotNull(renamed);
        assertEquals("~" + newName.toUpperCase(), renamed.getKey());
    }

    @Test
    public void testArchiveIfNeededWhenCaseOnlyChanges() throws Exception {
        String oldName = "mario";
        String newName = "MaRiO";
        assertTrue("The user rename should be performed", dao.archiveIfNeeded(DIRECTORY_ID, oldName, newName));
        verifyZeroInteractions(stashUserDao);
    }

    @Test(expected = UserAlreadyExistsException.class)
    public void testArchiveIfNeededWhenCrowdBackedUserExistsInSameDirectory() throws Exception {
        when(stashUserDao.findByName(USERNAME)).thenReturn(new InternalNormalUser.Builder(stashUser).crowdUser(crowdUser).build());
        dao.archiveIfNeeded(DIRECTORY_ID, "some-other-user", USERNAME);
    }

    @Test
    public void testArchiveIfNeededWhenCrowdBackedUserExistsInHigherDirectory() throws Exception {
        InternalNormalUser existingUser = new InternalNormalUser.Builder(stashUser).crowdUser(crowdUser).build();
        when(stashUserDao.findByName(USERNAME)).thenReturn(existingUser);
        assertFalse("The user rename should not be performed", dao.archiveIfNeeded(LOW_DIRECTORY_ID, "some-other-user", USERNAME));
    }

    @Test
    public void testArchiveIfNeededWhenCrowdBackedUserExistsInLowerDirectory() throws Exception {
        InternalNormalUser existingUser = new InternalNormalUser.Builder(stashUser).crowdUser(crowdUser).build();
        when(stashUserDao.findByName(USERNAME)).thenReturn(existingUser);
        when(stashUserDao.archive(Matchers.any(InternalNormalUser.class))).thenAnswer(MockitoUtils.returnFirst());
        assertTrue("The user rename should be performed", dao.archiveIfNeeded(HIGH_DIRECTORY_ID, "some-other-user", USERNAME));
        verify(stashUserDao).archive(eq(stashUser));
    }

    @Test
    public void testArchiveIfNeededWhenDeletedUserExists() throws Exception {
        when(stashUserDao.findByName(USERNAME)).thenReturn(stashUser);
        when(stashUserDao.archive(Matchers.any(InternalNormalUser.class))).thenAnswer(MockitoUtils.returnFirst());
        assertTrue("The user rename should be performed", dao.archiveIfNeeded(DIRECTORY_ID, "some-other-user", USERNAME));
        verify(stashUserDao).archive(eq(stashUser));
    }

    @Test
    public void testArchiveIfNeededWhenNoUserExists() throws Exception {
        when(stashUserDao.findByName(USERNAME)).thenReturn(null);
        assertTrue("The user rename should be performed", dao.archiveIfNeeded(DIRECTORY_ID, "some-other-user", USERNAME));
        verify(stashUserDao, never()).archive(Matchers.any(InternalNormalUser.class));
    }

}
