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

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.internal.ssh.dao.SshConfigurationDao;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionValidationService;
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.ssh.*;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.TestApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.sal.api.auth.AuthenticationController;
import com.atlassian.sal.api.auth.LoginUriProvider;
import com.atlassian.sal.api.xsrf.XsrfTokenValidator;
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.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

import static com.atlassian.bitbucket.internal.ssh.servlet.AddKeyRequestHandler.*;
import static com.atlassian.bitbucket.internal.ssh.servlet.SshKeyForm.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class AddKeyRequestHandlerTest {

    private static final String BASE_URL = "https://bitbucket.example.com/";
    private static final ApplicationUser USER = new TestApplicationUser("alice");
    private static final Project PROJECT = new InternalNormalProject.Builder().id(888).name("project").key("projectKey").build();
    private static final Repository REPOSITORY = new InternalRepository.Builder().id(666).name("repo").project((InternalProject) PROJECT).build();
    private static final String TEXT = "AAAAB3NzaC1yc2EAAAABIwAAAQEAwQ6EygyBdZwLgHxt2CDXypmlZyOtfWwPTMMTfX29PmLqcGrsLJmXY/A5RawENKRy9"
            + "saaPPgsyml8dvnjSDqY1H5s7P3K62twM9d0cbME1nJc8xTt8krCQqHUW4UZ4rRfjYTH7XoF89LmBxHPDWIz+k9JAgMutHPr6S4k"
            + "cN6bCnuX/tA3wArAJ7+EXLRZATKUAm2eMPJxRWA9N7WQ5KUHfY+KjzXwv0xdVBNnO6pLpb9V4Ht9vn3LKrEFnb/1SWnh5hdjTnn"
            + "S7VKj+qUYNh4mEt+Yvi3UchtR3cLbRhC1BpTtbmRu+AccUTnWgRIXelRXXwnb7GPs8DvtvxHvXxv0YQ==";
    private static final String LABEL = "This is a key label!";
    private static final String TEXT_WITH_LABEL = "ssh-rsa " + TEXT + " " + LABEL;
    private static final String URL_LIST_KEYS_IN_USER_ADMIN = "/admin/list/keys/for/specific/user";
    private static final String URL_LIST_KEYS_IN_USER_PROFILE = "/ssh/list/keys/for/current/user";
    private static final String URL_LIST_KEYS_IN_PROJECT_SETTINGS = "/ssh/list/keys/for/project";
    private static final String URL_LIST_KEYS_IN_REPO_SETTINGS = "/ssh/list/keys/for/repo";

    @Mock
    private AuthenticationController authenticationController;
    @Mock
    private SshAccessKeyService sshKeyAccessService;
    @InjectMocks
    private AddKeyRequestHandler handler;
    @Spy
    @SuppressWarnings("unused")
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private LoginUriProvider loginUriProvider;
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private NavBuilder navBuilder;
    @Mock
    private PermissionValidationService permissionValidationService;
    @Mock
    private ProjectService projectService;
    @Mock
    private RepositoryService repositoryService;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    @Mock
    private ResponseRenderer responseRenderer;
    @Mock
    private SshKeyService sshKeyService;
    @Mock
    private SshConfigurationDao sshConfigDao;
    @Mock
    private SshConfiguration sshConfig;
    @Mock
    private UserService userService;
    @Mock
    private XsrfTokenValidator xsrfTokenValidator;

    @Before
    public void setup() {
        given(navBuilder.admin().users().view(USER.getSlug()).listSshKeys().buildRelative()).willReturn(URL_LIST_KEYS_IN_USER_ADMIN);
        given(navBuilder.pluginServlets().path("ssh", "account", "keys").buildRelative()).willReturn(URL_LIST_KEYS_IN_USER_PROFILE);
        given(navBuilder.pluginServlets().path("ssh", "projects", PROJECT.getKey(), "keys").buildRelative()).willReturn(URL_LIST_KEYS_IN_PROJECT_SETTINGS);
        given(navBuilder.pluginServlets().path("ssh", "projects", PROJECT.getKey(), "repos", REPOSITORY.getSlug(), "keys").buildRelative()).willReturn(URL_LIST_KEYS_IN_REPO_SETTINGS);
        given(navBuilder.buildAbsolute()).willReturn(BASE_URL);
        given(userService.getUserByName(USER.getSlug())).willReturn(USER);
        given(projectService.getByKey(PROJECT.getKey())).willReturn(PROJECT);
        given(repositoryService.getBySlug(PROJECT.getKey(), REPOSITORY.getSlug())).willReturn(REPOSITORY);
        given(sshConfigDao.get()).willReturn(sshConfig);
        given(sshConfig.isEnabled()).willReturn(true);
        given(sshConfig.isAccessKeysEnabled()).willReturn(true);
    }

    @Test
    public void shouldShowAddKeyDialogForUserOnGetForProfile() throws Exception {
        handler.get(new SshKeysRequest(false, null, null, USER, null, null), request, response);

        Map<String, Object> expectedMap = ImmutableMap.<String, Object>builder()
                .put(KEY_USER, USER)
                .put(KEY_CANCEL_URL, URL_LIST_KEYS_IN_USER_PROFILE)
                .put(KEY_ASK_PERMISSION, false)
                .build();
        verify(responseRenderer).renderToResponse(request, response, SshKeysRequestHandler.MODULE_KEY, TEMPLATE_ADD_KEY, expectedMap);
    }

    @Test
    public void shouldShowAddKeyDialogForUserOnGetForAdmin() throws Exception {
        handler.get(new SshKeysRequest(true, null, null, USER, null, null), request, response);

        Map<String, Object> expectedMap = ImmutableMap.<String, Object>builder()
                .put(KEY_USER, USER)
                .put(KEY_CANCEL_URL, URL_LIST_KEYS_IN_USER_ADMIN)
                .put(KEY_ASK_PERMISSION, false)
                .build();
        verify(responseRenderer).renderToResponse(request, response, SshKeysRequestHandler.MODULE_KEY,
                TEMPLATE_ADD_KEY, expectedMap);
    }

    @Test
    public void shouldShowAddKeyDialogForRepositoryOnGet() throws Exception {
        handler.get(new SshKeysRequest(false, null, REPOSITORY, null, null, null), request, response);

        Map<String, Object> expectedMap = ImmutableMap.<String, Object>builder()
                .put(KEY_REPOSITORY, REPOSITORY)
                .put(KEY_CANCEL_URL, URL_LIST_KEYS_IN_REPO_SETTINGS)
                .put(KEY_ASK_PERMISSION, true)
                .build();
        verify(responseRenderer).renderToResponse(request, response, SshKeysRequestHandler.MODULE_KEY,
                TEMPLATE_ADD_KEY, expectedMap);
    }

    @Test
    public void shouldShowAddKeyDialogForProjectOnGet() throws Exception {
        handler.get(new SshKeysRequest(false, PROJECT, null, null, null, null), request, response);

        Map<String, Object> expectedMap = ImmutableMap.<String, Object>builder()
                .put(KEY_PROJECT, PROJECT)
                .put(KEY_CANCEL_URL, URL_LIST_KEYS_IN_PROJECT_SETTINGS)
                .put(KEY_ASK_PERMISSION, true)
                .build();
        verify(responseRenderer).renderToResponse(request, response, SshKeysRequestHandler.MODULE_KEY,
                TEMPLATE_ADD_KEY, expectedMap);
    }

    @Test
    public void shouldPersistKeyForUserFromProfile() throws Exception {
        handler.post(new SshKeysRequest(false, null, null, USER, new SshKeyForm(TEXT, true), null),
                request, response);

        verify(sshKeyService).addForUser(USER, TEXT, "");
        verify(response).sendRedirect(URL_LIST_KEYS_IN_USER_PROFILE);
    }

    @Test
    public void shouldPersistKeyForUserFromAdmin() throws Exception {
        handler.post(new SshKeysRequest(true, null, null, USER, new SshKeyForm(TEXT, true), null),
                request, response);

        verify(sshKeyService).addForUser(USER, TEXT, "");
        verify(response).sendRedirect(URL_LIST_KEYS_IN_USER_ADMIN);
    }

    @Test
    public void shouldPersistReadOnlyKeyForProjectInParam() throws Exception {
        shouldPersistKeyForProjectInParam(true, Permission.PROJECT_READ);
    }

    @Test
    public void shouldPersistReadWriteKeyForProjectInParam() throws Exception {
        shouldPersistKeyForProjectInParam(false, Permission.PROJECT_WRITE);
    }

    @Test
    public void shouldPersistReadOnlyKeyForRepositoryInParam() throws Exception {
        shouldPersistKeyForRepositoryInParam(true, Permission.REPO_READ);
    }

    @Test
    public void shouldPersistReadWriteKeyForRepositoryInParam() throws Exception {
        shouldPersistKeyForRepositoryInParam(false, Permission.REPO_WRITE);
    }

    @Test
    public void shouldExtractLabelFromKey() throws Exception {
        handler.post(new SshKeysRequest(false, null, null, USER, new SshKeyForm(TEXT_WITH_LABEL, true), null),
                request, response);

        verify(sshKeyService).addForUser(any(ApplicationUser.class), eq(TEXT_WITH_LABEL), eq(LABEL));
    }

    @Test
    public void shouldRenderErrorOnDuplicateKeyException() throws Exception {
        given(sshKeyService.addForUser(USER, TEXT, "")).willThrow(
                new DuplicatePublicKeyException(new KeyedMessage("msgKey", "dupe public key localized message", "rootMsg")));

        handler.post(new SshKeysRequest(false, null, null, USER, new SshKeyForm(TEXT, true), null),
                request, response);

        Map<String, Object> expectedMap = ImmutableMap.<String, Object>builder()
                .put("form", ImmutableMap.<String, Object>builder()
                        .put(KEY_TEXT, TEXT)
                        .put(KEY_LABEL, "")
                        .put(KEY_READ_ONLY, true)
                        .build())
                .put(KEY_CANCEL_URL, URL_LIST_KEYS_IN_USER_PROFILE)
                .put("formErrors", Arrays.asList("dupe public key localized message"))
                .put(KEY_ASK_PERMISSION, false)
                .put(KEY_USER, USER)
                .build();

        verify(responseRenderer).renderToResponse(request, response, SshKeysRequestHandler.MODULE_KEY,
                TEMPLATE_ADD_KEY, expectedMap);
    }

    @Test
    public void shouldGiveConflictIfSshDisabledAndKeyAccessAdded() throws Exception {
        given(sshConfig.isEnabled()).willReturn(false);

        handler.post(new SshKeysRequest(false, null, REPOSITORY, null, new SshKeyForm(TEXT, true), null), request, response);

        verify(response).sendError(HttpServletResponse.SC_CONFLICT);
    }

    @Test
    public void shouldGiveConflictIfAccessKeysDisabledAndKeyAccessAdded() throws Exception {
        given(sshConfig.isAccessKeysEnabled()).willReturn(false);

        handler.post(new SshKeysRequest(false, null, REPOSITORY, null, new SshKeyForm(TEXT, true), null), request, response);

        verify(response).sendError(HttpServletResponse.SC_CONFLICT);
    }

    @Test
    public void shouldRedirectOnSuccessToProvidedUrlIfPresent() throws Exception {
        String redirectUrl = "/redirect-url";
        handler.post(new SshKeysRequest(false, null, null, USER, new SshKeyForm(TEXT, true), redirectUrl),
                request, response);

        verify(sshKeyService).addForUser(USER, TEXT, "");
        verify(response).sendRedirect(redirectUrl);
    }

    @Test
    public void shouldRedirectToSafeUrlIfUnsafeUrlProvided() throws Exception {
        String badRedirectUrl = "http://bad-url.com/";
        handler.post(new SshKeysRequest(false, null, null, USER, new SshKeyForm(TEXT, true), badRedirectUrl),
                request, response);

        verify(sshKeyService).addForUser(USER, TEXT, "");
        verify(response).sendRedirect(BASE_URL);
    }

    private void shouldPersistKeyForRepositoryInParam(boolean readOnly, Permission permission) throws ServletException, IOException {
        handler.post(new SshKeysRequest(false, null, REPOSITORY, null, new SshKeyForm(TEXT, readOnly), null),
                request, response);

        final SetSshAccessKeyRequest repositoryAccessRequest = new SetSshAccessKeyRequest.Builder()
                .repositoryAccess(REPOSITORY, permission)
                .key(TEXT, "")
                .build();
        verify(sshKeyAccessService).set(repositoryAccessRequest);
        verify(response).sendRedirect(URL_LIST_KEYS_IN_REPO_SETTINGS);
    }

    private void shouldPersistKeyForProjectInParam(boolean readOnly, Permission permission) throws ServletException, IOException {
        handler.post(new SshKeysRequest(false, PROJECT, null, null, new SshKeyForm(TEXT, readOnly), null),
                request, response);

        SetSshAccessKeyRequest projectAccessRequest = new SetSshAccessKeyRequest.Builder()
                .projectAccess(PROJECT, permission).key(TEXT, "").build();
        verify(sshKeyAccessService).set(projectAccessRequest);
        verify(response).sendRedirect(URL_LIST_KEYS_IN_PROJECT_SETTINGS);
    }
}
