package com.atlassian.bitbucket.internal.ssh.fragments;

import com.atlassian.bitbucket.hamcrest.PageMatchers;
import com.atlassian.bitbucket.internal.key.ssh.TestSshAccessKey;
import com.atlassian.bitbucket.internal.ssh.dao.SshConfigurationDao;
import com.atlassian.bitbucket.internal.ssh.server.HostKeyPairProvider;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.ssh.*;
import com.atlassian.bitbucket.ssh.api.TestSshKey;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.TestApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.stash.internal.project.InternalNormalProject;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

import static org.junit.Assert.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;

@RunWith(MockitoJUnitRunner.class)
public class SshKeysContextProviderTest {

    private static final Project PROJECT = new InternalNormalProject.Builder().name("project").key("projectKey").build();
    private static final Repository REPOSITORY = new InternalRepository.Builder().name("repo").project((InternalProject) PROJECT).build();
    private static final ApplicationUser USER1 = new TestApplicationUser("alice");
    private static final ApplicationUser USER2 = new TestApplicationUser("bob");
    private static final Map<String, Object> NO_CONTEXT = Collections.emptyMap();
    private static final SshKey KEY_ONE = new TestSshKey("one");
    private static final SshKey KEY_TWO = new TestSshKey("two");
    private static final SshKey KEY_THREE = new TestSshKey("three");
    private static final PageRequest PAGE_REQUEST = PageUtils.newRequest(1, 100);
    private static final String ADMIN_ADD_USER_KEY_URL = "/admin/users/specific-key/keys/add";
    private static final String PROFILE_ADD_USER_KEY_URL = "/users/current-key/keys/add";
    private static final String ADD_PROJECT_KEY_URL = "/projects/projectKey/add";
    private static final String ADD_REPOSITORY_KEY_URL = "/projects/projectKey/repos/repo/add";

    @Mock
    private SshAccessKeyService keyAccessService;

    @Mock
    private SshKeyService keyService;

    @Mock
    private HostKeyPairProvider keyPairProvider;

    @Mock
    private SshConfigurationDao sshConfigurationDao;

    @Mock
    private SshConfiguration sshConfiguration;

    @Mock
    private UserService userService;

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private NavBuilder navBuilder;

    @InjectMocks
    private SshKeysContextProvider contextProvider;

    @Before
    public void setup() {
        given(navBuilder.pluginServlets().path("ssh", "account", "keys", "add").buildRelative()).willReturn(PROFILE_ADD_USER_KEY_URL);
        given(navBuilder.pluginServlets().path("ssh", "admin", "users", USER2.getSlug(), "keys", "add").buildRelative()).willReturn(ADMIN_ADD_USER_KEY_URL);
        given(navBuilder.pluginServlets().path("ssh", "projects", PROJECT.getKey(), "keys", "add").buildRelative()).willReturn(ADD_PROJECT_KEY_URL);
        given(navBuilder.pluginServlets().path("ssh", "projects", PROJECT.getKey(), "repos", REPOSITORY.getSlug(), "keys", "add")
                        .buildRelative()).willReturn(ADD_REPOSITORY_KEY_URL);
        given(keyPairProvider.getFingerprint()).willReturn("aabbcc");
        given(keyPairProvider.getAlgorithm()).willReturn("algo");
        given(sshConfigurationDao.get()).willReturn(sshConfiguration);
        given(sshConfiguration.isEnabled()).willReturn(true);
        given(sshConfiguration.isAccessKeysEnabled()).willReturn(true);
    }

    @Test
    public void shouldIncludeAnyExistingContext() throws Exception {
        Map<String, Object> existingContext = ImmutableMap.<String, Object>builder()
                                                          .put("prop1", "val1")
                                                          .put("prop2", "val2")
                                                          .build();
        givenKeyRequestForUserReturnsNoKeys(USER1);

        Map<String, Object> context = contextProvider.createKeyTableContext(existingContext, USER1);

        assertEquals("val1", context.get("prop1"));
        assertEquals("val2", context.get("prop2"));
    }

    @Test
    public void shouldGetUserFromContextAndIncludeUserKeysInContextWhenCalledWithoutResourceArgument() throws Exception {
        ImmutableMap<String, Object> contextWithUser = ImmutableMap.<String, Object>builder().put("user", USER2).build();
        givenKeyRequestForUserReturns(USER2, KEY_ONE, KEY_TWO);

        Map<String, Object> context = contextProvider.getContextMap(contextWithUser);

        assertContextContainsKeys(context, KEY_ONE, KEY_TWO);
        assertEquals(USER2, context.get("user"));
        assertEquals(ADMIN_ADD_USER_KEY_URL, context.get("addKeyUrl"));
    }

    @Test
    public void shouldIncludeUserKeysInUserContext() throws Exception {
        givenKeyRequestForUserReturns(USER1, KEY_ONE, KEY_TWO);

        Map<String, Object> context = contextProvider.createKeyTableContext(NO_CONTEXT, USER1);

        assertContextContainsKeys(context, KEY_ONE, KEY_TWO);
        assertEquals(USER1, context.get("user"));
        assertEquals(PROFILE_ADD_USER_KEY_URL, context.get("addKeyUrl"));
    }

    @Test
    public void shouldIncludeProjectKeysInProjectContext() throws Exception {
        TestSshAccessKey keyAccessOne = TestSshAccessKey.readOnlyAccess(PROJECT, KEY_ONE);
        TestSshAccessKey keyAccessTwo = TestSshAccessKey.readOnlyAccess(PROJECT, KEY_THREE);

        givenKeyRequestForProjectReturns(PROJECT, keyAccessOne, keyAccessTwo);

        Map<String, Object> context = contextProvider.createKeyTableContext(NO_CONTEXT, PROJECT);

        assertContextContainsKeyAccesses(context, keyAccessOne, keyAccessTwo);
        assertEquals(PROJECT, context.get("project"));
        assertEquals(ADD_PROJECT_KEY_URL, context.get("addKeyUrl"));
    }

    @Test
    public void shouldIncludeRepoKeysInRepoContext() throws Exception {
        TestSshAccessKey keyAccessOne = TestSshAccessKey.readOnlyAccess(REPOSITORY, KEY_ONE);
        TestSshAccessKey keyAccessTwo = TestSshAccessKey.readOnlyAccess(REPOSITORY, KEY_THREE);

        givenKeyRequestForRepoReturns(REPOSITORY, keyAccessOne, keyAccessTwo);

        Map<String, Object> context = contextProvider.createKeyTableContext(NO_CONTEXT, REPOSITORY);

        assertContextContainsKeyAccesses(context, keyAccessOne, keyAccessTwo);
        assertEquals(REPOSITORY, context.get("repository"));
        assertEquals(ADD_REPOSITORY_KEY_URL, context.get("addKeyUrl"));
    }

    @Test
    public void shouldIncludeFormattedFingerprintAndAlgorithmIfThereAreKeys() throws Exception {
        givenKeyRequestForUserReturns(USER1, KEY_ONE, KEY_TWO);

        Map<String, Object> context = contextProvider.createKeyTableContext(NO_CONTEXT, USER1);

        assertEquals("aa:bb:cc", context.get("fingerprint"));
        assertEquals("algo", context.get("algorithm"));
    }

    @Test
    public void shouldNotIncludeFingerPrintAndAlgorithmIfThereAreNoKeys() throws Exception {
        givenKeyRequestForUserReturnsNoKeys(USER1);

        Map<String, Object> context = contextProvider.createKeyTableContext(NO_CONTEXT, USER1);

        assertFalse(context.containsKey("fingerprint"));
        assertFalse(context.containsKey("algorithm"));
    }

    private void givenKeyRequestForUserReturnsNoKeys(ApplicationUser user) {
        givenKeyRequestForUserReturns(user);
    }

    private void givenKeyRequestForUserReturns(ApplicationUser user, SshKey... keys) {
        given(keyService.findAllForUser(same(user), any(PageRequest.class)))
                .willReturn(PageUtils.createPage(Arrays.asList(keys), true, PAGE_REQUEST));
    }

    private void givenKeyRequestForProjectReturns(Project project, SshAccessKey... keys) {
        given(keyAccessService.findByProject(same(project), any(PageRequest.class)))
                .willReturn(PageUtils.createPage(Arrays.asList(keys), true, PAGE_REQUEST));
    }

    private void givenKeyRequestForRepoReturns(Repository repo, SshAccessKey... keys) {
        given(keyAccessService.findByRepository(same(repo), any(PageRequest.class)))
                .willReturn(PageUtils.createPage(Arrays.asList(keys), true, PAGE_REQUEST));
    }

    @SuppressWarnings("unchecked")
    private void assertContextContainsKeys(Map<String, Object> context, SshKey... keys) {
        assertThat((Page<SshKey>) context.get("keyPage"), PageMatchers.isPageOf(keys));
    }

    @SuppressWarnings("unchecked")
    private void assertContextContainsKeyAccesses(Map<String, Object> context, SshAccessKey... keyAccesses) {
        assertThat((Page<SshAccessKey>) context.get("keyPage"), PageMatchers.isPageOf(keyAccesses));
    }
}