package com.atlassian.stash.internal.audit.external;

import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.embedded.api.DirectoryType;
import com.atlassian.crowd.embedded.api.PasswordCredential;
import com.atlassian.crowd.event.directory.DirectoryCreatedEvent;
import com.atlassian.crowd.event.directory.DirectoryDeletedEvent;
import com.atlassian.crowd.event.group.*;
import com.atlassian.crowd.event.user.*;
import com.atlassian.crowd.model.group.Group;
import com.atlassian.crowd.model.membership.MembershipType;
import com.atlassian.crowd.model.user.User;
import com.atlassian.bitbucket.audit.Priority;
import com.atlassian.bitbucket.event.audit.AuditEvent;
import com.atlassian.stash.internal.audit.AbstractAuditEventListenerTest;
import org.junit.Test;
import org.mockito.InjectMocks;

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

public class CrowdEventListenerTest extends AbstractAuditEventListenerTest {

    private static final String TARGET_USERNAME = "target-user";
    private static final String DIRECTORY = "directory";
    private static final String GROUP = "group";

    @InjectMocks
    private CrowdEventListener listener;

    @Test
    public void testOnInternalDirectoryCreated() throws Exception {
        listener.onDirectoryCreated(new DirectoryCreatedEvent(this, mockInternalDirectory()));

        assertStandardAuditData(DIRECTORY, Priority.HIGH);
    }

    @Test
    public void testOnExternalDirectoryCreated() throws Exception {
        listener.onDirectoryCreated(new DirectoryCreatedEvent(this, mockExternalDirectory()));

        assertStandardAuditData(DIRECTORY, Priority.HIGH);
    }

    @Test
    public void testOnInternalDirectoryDeleted() throws Exception {
        listener.onDirectoryDeleted(new DirectoryDeletedEvent(this, mockInternalDirectory()));

        assertStandardAuditData(DIRECTORY, Priority.HIGH);
    }

    @Test
    public void testOnExternalDirectoryDeleted() throws Exception {
        listener.onDirectoryDeleted(new DirectoryDeletedEvent(this, mockExternalDirectory()));

        assertStandardAuditData(DIRECTORY, Priority.HIGH);
    }

    @Test
    public void testOnGroupCreatedInInternalDirectory() throws Exception {
        listener.onGroupCreated(new GroupCreatedEvent(this, mockInternalDirectory(), mockGroup()));

        assertStandardAuditData(GROUP, Priority.HIGH);
    }

    @Test
    public void testOnGroupCreatedInExternalDirectory() throws Exception {
        listener.onGroupCreated(new GroupCreatedEvent(this, mockExternalDirectory(), mockGroup()));

        assertNoAuditEvent();
    }

    @Test
    public void testOnGroupUpdatedInInternalDirectory() throws Exception {
        listener.onGroupUpdated(new GroupUpdatedEvent(this, mockInternalDirectory(), mockGroup()));

        assertStandardAuditData(GROUP, Priority.HIGH);
    }

    @Test
    public void testOnGroupUpdatedInExternalDirectory() throws Exception {
        listener.onGroupUpdated(new GroupUpdatedEvent(this, mockExternalDirectory(), mockGroup()));

        assertNoAuditEvent();
    }

    @Test
    public void testOnGroupDeletedInInternalDirectory() throws Exception {
        listener.onGroupDeleted(new GroupDeletedEvent(this, mockInternalDirectory(), GROUP));

        assertStandardAuditData(GROUP, Priority.HIGH);
    }

    @Test
    public void testOnGroupDeletedInExternalDirectory() throws Exception {
        listener.onGroupDeleted(new GroupDeletedEvent(this, mockExternalDirectory(), GROUP));

        assertNoAuditEvent();
    }

    @Test
    public void testOnGroupMembershipCreatedInInternalDirectory() throws Exception {
        listener.onGroupMembershipCreated(
                new GroupMembershipCreatedEvent(this, mockInternalDirectory(), TARGET_USERNAME, GROUP, MembershipType.GROUP_USER));

        assertStandardAuditData(GROUP, Priority.HIGH);
    }

    @Test
    public void testOnGroupMembershipCreatedInExternalDirectory() throws Exception {
        listener.onGroupMembershipCreated(
                new GroupMembershipCreatedEvent(this, mockExternalDirectory(), TARGET_USERNAME, GROUP, MembershipType.GROUP_USER));

        assertNoAuditEvent();
    }

    @Test
    public void testOnGroupMembershipDeletedInInternalDirectory() throws Exception {
        listener.onGroupMembershipDeleted(
                new GroupMembershipDeletedEvent(this, mockInternalDirectory(), TARGET_USERNAME, GROUP, MembershipType.GROUP_USER));

        assertStandardAuditData(GROUP, Priority.HIGH);
    }

    @Test
    public void testOnGroupMembershipDeletedInExternalDirectory() throws Exception {
        listener.onGroupMembershipDeleted(
                new GroupMembershipDeletedEvent(this, mockExternalDirectory(), TARGET_USERNAME, GROUP, MembershipType.GROUP_USER));

        assertNoAuditEvent();
    }

    @Test
    public void testOnUserCreatedInInternalDirectory() throws Exception {
        listener.onUserCreated(new UserCreatedEvent(this, mockInternalDirectory(), mockUser()));

        assertStandardAuditData(TARGET_USERNAME, Priority.HIGH);
    }

    @Test
    public void testOnUserCreatedInExternalDirectory() throws Exception {
        listener.onUserCreated(new UserCreatedEvent(this, mockExternalDirectory(), mockUser()));

        assertNoAuditEvent();
    }

    @Test
    public void testOnUserCredentialUpdatedInInternalDirectory() throws Exception {
        listener.onUserCredentialUpdated(new UserCredentialUpdatedEvent(this, mockInternalDirectory(),
                TARGET_USERNAME, PasswordCredential.encrypted("")));

        assertStandardAuditData(TARGET_USERNAME, Priority.HIGH);
    }

    @Test
    public void testOnUserCredentialUpdatedInExternalDirectory() throws Exception {
        listener.onUserCredentialUpdated(new UserCredentialUpdatedEvent(this, mockExternalDirectory(),
                TARGET_USERNAME, PasswordCredential.encrypted("")));

        assertStandardAuditData(TARGET_USERNAME, Priority.MEDIUM);
    }

    @Test
    public void testOnUserDeletedInInternalDirectory() throws Exception {
        listener.onUserDeleted(new UserDeletedEvent(this, mockInternalDirectory(), TARGET_USERNAME));

        assertStandardAuditData(TARGET_USERNAME, Priority.HIGH);
    }

    @Test
    public void testOnUserDeletedInExternalDirectory() throws Exception {
        listener.onUserDeleted(new UserDeletedEvent(this, mockExternalDirectory(), TARGET_USERNAME));

        assertNoAuditEvent();
    }

    @Test
    public void testOnUserRenamedInInternalDirectory() throws Exception {
        listener.onUserRenamed(new UserRenamedEvent(this, mockInternalDirectory(), mockUser(), TARGET_USERNAME + "_old"));

        assertStandardAuditData(TARGET_USERNAME, Priority.HIGH);
    }

    @Test
    public void testOnUserRenamedInExternalDirectory() throws Exception {
        listener.onUserRenamed(new UserRenamedEvent(this, mockExternalDirectory(), mockUser(), TARGET_USERNAME + "_old"));

        assertNoAuditEvent();
    }

    private static Directory mockInternalDirectory() {
        Directory directory = mock(Directory.class);
        when(directory.getName()).thenReturn(DIRECTORY);
        when(directory.getType()).thenReturn(DirectoryType.INTERNAL);
        return directory;
    }

    private static Directory mockExternalDirectory() {
        Directory directory = mock(Directory.class);
        when(directory.getName()).thenReturn(DIRECTORY);
        when(directory.getType()).thenReturn(DirectoryType.CONNECTOR);
        return directory;
    }

    private static Group mockGroup() {
        Group group = mock(Group.class);
        when(group.getName()).thenReturn(GROUP);
        return group;
    }

    private static User mockUser() {
        User user = mock(User.class);
        when(user.getName()).thenReturn(TARGET_USERNAME);
        return user;
    }

    private void assertStandardAuditData(String target, Priority priority) {
        assertStandardAuditData(target, USERNAME, priority);
    }

    @SuppressWarnings("ConstantConditions")
    private void assertStandardAuditData(String target, String eventUser, Priority priority) {
        AuditEvent event = getAuditEvent();
        assertNotNull(event.getEntry());
        assertEquals(eventUser, event.getEntry().getUser().getName());
        assertEquals(target, event.getEntry().getTarget());
        assertEventCommonDetails(event, priority);
    }
}
