package com.atlassian.bitbucket.internal.key.ssh.dao.v3;

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.activeobjects.external.ModelVersion;
import com.atlassian.activeobjects.test.TestActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDaoTest;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.internal.key.ssh.ResourceType;
import com.atlassian.bitbucket.internal.key.ssh.dao.AoSshKey;
import com.atlassian.bitbucket.internal.key.ssh.dao.v1.AoSshAccessKeyV1;
import com.atlassian.bitbucket.internal.key.ssh.dao.v1.AoSshKeyV1;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionAdminService;
import com.atlassian.bitbucket.permission.SetPermissionRequest;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.project.ProjectService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.user.*;
import com.atlassian.bitbucket.util.Operation;
import net.java.ao.DBParam;
import net.java.ao.EntityManager;
import net.java.ao.test.jdbc.Data;
import net.java.ao.test.jdbc.DatabaseUpdater;
import net.java.ao.test.jdbc.NonTransactional;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.sql.SQLException;

import static com.atlassian.bitbucket.internal.key.ssh.dao.Keys.*;
import static com.atlassian.bitbucket.internal.key.ssh.dao.v1.AoSshAccessKeyV1.*;
import static com.atlassian.bitbucket.internal.key.ssh.dao.v1.AoSshKeyV1.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

@Data(MigrateAccessKeysToServiceUsersTaskTest.KeysUpdater.class)
public class MigrateAccessKeysToServiceUsersTaskTest extends AbstractAoDaoTest {

    private PermissionAdminService permissionAdminService;
    private UserAdminService userAdminService;
    private ProjectService projectService;
    private RepositoryService repositoryService;
    private SecurityService securityService;

    private ActiveObjects ao;
    private ServiceUser serviceUser1;
    private ServiceUser serviceUser2;
    private Project project1;
    private Project project2;
    private Repository repository1;
    private Repository repository2;
    private I18nService i18nService;

    @Before
    public void setUp() throws Throwable {
        i18nService = mock(I18nService.class);
        when(i18nService.createKeyedMessage(anyString(), anyVararg())).thenReturn(new KeyedMessage("", "", ""));

        EscalatedSecurityContext securityContext = mock(EscalatedSecurityContext.class);
        securityService = mock(SecurityService.class);
        when(securityService.withPermission(any(Permission.class), anyString())).thenReturn(securityContext);
        when(securityContext.call(any(Operation.class))).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return ((Operation) invocation.getArguments()[0]).perform();
            }
        });

        userAdminService = mock(UserAdminService.class);
        serviceUser1 = mock(ServiceUser.class);
        when(serviceUser1.getId()).thenReturn(4);
        serviceUser2 = mock(ServiceUser.class);
        when(serviceUser2.getId()).thenReturn(5);
        when(userAdminService.createServiceUser(isA(ServiceUserCreateRequest.class)))
                .thenReturn(serviceUser1)
                .thenReturn(serviceUser2);

        permissionAdminService = mock(PermissionAdminService.class);

        projectService = mock(ProjectService.class);
        project1 = mock(Project.class);
        when(projectService.getById(1)).thenReturn(project1);

        project2 = mock(Project.class);
        when(projectService.getById(2)).thenReturn(project2);

        repositoryService = mock(RepositoryService.class);
        repository1 = mock(Repository.class);
        when(repositoryService.getById(1)).thenReturn(repository1);
        repository2 = mock(Repository.class);
        when(repositoryService.getById(2)).thenReturn(repository2);

        ao = new TestActiveObjects(entityManager);
    }

    @Test
    @NonTransactional
    public void testUpgrade() throws Exception {
        new MigrateAccessKeysToServiceUsersTask(i18nService, permissionAdminService, projectService, repositoryService,
                securityService, userAdminService).upgrade(ModelVersion.valueOf("2"), ao);

        //None should have been deleted
        assertEquals(5, ao.count(AoSshKey.class));
        assertEquals(0, ao.count(AoSshKey.class, AoSshKey.COLUMN_USER_ID + " is null"));

        verify(permissionAdminService).setPermission(new SetPermissionRequest.Builder().user(serviceUser1)
                .projectPermission(Permission.PROJECT_READ, project1).build());
        verify(permissionAdminService).setPermission(new SetPermissionRequest.Builder().user(serviceUser1)
                .repositoryPermission(Permission.REPO_WRITE, repository1).build());

        verify(permissionAdminService).setPermission(new SetPermissionRequest.Builder().user(serviceUser2)
                .projectPermission(Permission.PROJECT_WRITE, project2).build());
        verify(permissionAdminService).setPermission(new SetPermissionRequest.Builder().user(serviceUser2)
                .repositoryPermission(Permission.REPO_READ, repository2).build());
    }

    public static class KeysUpdater implements DatabaseUpdater {

        @Override
        public void update(EntityManager entityManager) throws Exception {
            entityManager.migrate(AoSshKeyV1.class, AoSshAccessKeyV1.class);

            createKey(entityManager, 1, PUBLIC_KEY_1, PUBLIC_KEY_1_MD5);
            createKey(entityManager, 2, PUBLIC_KEY_3_WITH_TABS_AND_NEWLINES, PUBLIC_KEY_3_MD5);
            createKey(entityManager, 3, PUBLIC_KEY_3_WITH_TABS_AND_NEWLINES, PUBLIC_KEY_3_MD5);

            AoSshKeyV1 keyForAccess1 = createKey(entityManager, null, PUBLIC_KEY_3_WITH_TABS_AND_NEWLINES, PUBLIC_KEY_3_MD5);
            createKeyAccess(entityManager, keyForAccess1.getId(), ResourceType.PROJECT, 1, true);
            createKeyAccess(entityManager, keyForAccess1.getId(), ResourceType.REPOSITORY, 1, false);

            AoSshKeyV1 keyForAccess2 = createKey(entityManager, null, PUBLIC_KEY_4_WITH_LONG_LABEL, PUBLIC_KEY_4_MD5);
            createKeyAccess(entityManager, keyForAccess2.getId(), ResourceType.PROJECT, 2, false);
            createKeyAccess(entityManager, keyForAccess2.getId(), ResourceType.REPOSITORY, 2, true);
        }

        private AoSshAccessKeyV1 createKeyAccess(EntityManager entityManager, int keyId, ResourceType resourceType, int resourceId, boolean readOnly) throws SQLException {
            return entityManager.create(
                    AoSshAccessKeyV1.class,
                    new DBParam(COLUMN_KEY_ID, keyId),
                    new DBParam(COLUMN_RESOURCE_TYPE, resourceType),
                    new DBParam(COLUMN_RESOURCE_ID, resourceId),
                    new DBParam(COLUMN_IS_READ_ONLY, readOnly));
        }

        private AoSshKeyV1 createKey(EntityManager entityManager, Integer userId, String keyText, String md5) throws SQLException {
            return entityManager.create(
                    AoSshKeyV1.class,
                    new DBParam(COLUMN_USER_ID, userId),
                    new DBParam(COLUMN_KEY_TEXT, keyText),
                    new DBParam(COLUMN_KEY_MD5, md5)
            );
        }
    }
}
