package com.atlassian.stash.internal.auth;

import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.scm.AuthenticationState;
import com.atlassian.bitbucket.scm.http.HttpScmRequestHandler;
import com.atlassian.bitbucket.server.ApplicationMode;
import com.atlassian.bitbucket.server.ApplicationModeSupplier;
import com.atlassian.stash.internal.scm.http.HttpScmRequestHandlerProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.atlassian.bitbucket.scm.AuthenticationState.*;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ScmAuthenticationFailureHandlerTest {

    @Mock
    private DefaultHttpAuthenticationContext authenticationContext;
    @InjectMocks
    private ScmAuthenticationFailureHandler failureHandler;
    @Mock
    private I18nService i18nService;
    @Mock
    private ApplicationModeSupplier modeSupplier;
    @Mock
    private NavBuilder navBuilder;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    @Mock
    private HttpScmRequestHandler requestHandler;
    @Mock
    private HttpScmRequestHandlerProvider scmRequestProvider;

    @Before
    public void setup() throws Exception {
        when(authenticationContext.getRequest()).thenReturn(request);
        when(authenticationContext.getResponse()).thenReturn(response);

        when(request.getServletPath()).thenReturn(ScmAuthenticationFailureHandler.SCM_SERVLET_PATH);
        when(scmRequestProvider.getHandler(same(request))).thenReturn(of(requestHandler));
    }

    //Request/response pairs with no registered SCM handler should not be handled
    @Test
    public void testHandleUnhandledRequest() throws Exception {
        reset(scmRequestProvider); //Reset getHttpScmRequest(), trained in setup
        when(scmRequestProvider.getHandler(any(HttpServletRequest.class))).thenReturn(empty());
        when(authenticationContext.getAuthenticationState()).thenReturn(NOT_AUTHENTICATED);

        assertFalse(failureHandler.onAuthenticationFailure(authenticationContext));
    }

    //Paths that don't start /scm/ should not be handled
    @Test
    public void testHandleOutsideScm() throws Exception {
        when(authenticationContext.getAuthenticationState()).thenReturn(NOT_AUTHENTICATED);
        when(request.getServletPath()).thenReturn(""); //Reset getServletPath(), trained in setup
        assertFalse(failureHandler.onAuthenticationFailure(authenticationContext));
        when(request.getServletPath()).thenReturn("/scmmmm");
        assertFalse(failureHandler.onAuthenticationFailure(authenticationContext));
    }

    @Test
    public void testHandleFailedAuthentication() throws Exception {
        when(i18nService.getMessage(eq("bitbucket.scm.no.authentication"))).thenReturn("Title");
        when(i18nService.getMessage(eq("bitbucket.scm.no.authentication.detail"))).thenReturn("Message");
        when(authenticationContext.getAuthenticationState()).thenReturn(NOT_AUTHENTICATED);

        failureHandler.onAuthenticationFailure(authenticationContext);
        verifySendAuthenticationError(AuthenticationState.NOT_AUTHENTICATED, "Title", "Message");
    }

    @Test
    public void testHandleUnlicensedUser() throws Exception {
        when(i18nService.getMessage(eq("bitbucket.scm.no.licensed.user")))
                .thenReturn("Title");
        when(i18nService.getMessage(eq("bitbucket.scm.no.licensed.user.detail"), eq(Product.NAME)))
                .thenReturn("Message");
        when(authenticationContext.getAuthenticationState()).thenReturn(UNLICENSED);

        failureHandler.onAuthenticationFailure(authenticationContext);
        verifySendAuthenticationError(AuthenticationState.UNLICENSED, "Title", "Message");
    }

    @Test
    public void testHandleCaptchaUser() throws Exception {
        when(i18nService.getMessage(eq("bitbucket.scm.captcha.required")))
                .thenReturn("Title");
        when(i18nService.getMessage(eq("bitbucket.scm.captcha.required.detail"), eq(Product.NAME), eq("URL")))
                .thenReturn("Message");
        when(navBuilder.buildAbsolute()).thenReturn("URL");
        when(authenticationContext.getAuthenticationState()).thenReturn(CAPTCHA_REQUIRED);

        failureHandler.onAuthenticationFailure(authenticationContext);
        verifySendAuthenticationError(AuthenticationState.CAPTCHA_REQUIRED, "Title", "Message");
    }

    @Test
    public void testHandleCaptchaUserInMirrorMode() throws Exception {
        when(modeSupplier.getMode()).thenReturn(ApplicationMode.MIRROR);
        when(i18nService.getMessage(eq("bitbucket.scm.captcha.required")))
                .thenReturn("Title");
        when(i18nService.getMessage(eq("bitbucket.scm.captcha.required.upstream.detail"), eq(Product.NAME)))
                .thenReturn("Message for mirror");
        when(navBuilder.buildAbsolute()).thenReturn("URL");
        when(authenticationContext.getAuthenticationState()).thenReturn(CAPTCHA_REQUIRED);

        failureHandler.onAuthenticationFailure(authenticationContext);
        verifySendAuthenticationError(AuthenticationState.CAPTCHA_REQUIRED, "Title", "Message for mirror");
    }

    private void verifySendAuthenticationError(AuthenticationState state, String summary, String detailMessage) throws IOException {
        verify(requestHandler).sendAuthenticationError(eq(state), eq(summary), eq(detailMessage), same(request), same(response));
    }

}
