package com.atlassian.stash.internal.web.users;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.mail.MailException;
import com.atlassian.bitbucket.mail.MailService;
import com.atlassian.bitbucket.user.TestApplicationUser;
import com.atlassian.bitbucket.user.UserAdminService;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.stash.internal.crowd.CrowdControl;
import com.atlassian.stash.internal.spring.AbstractModelAndViewMatcher;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.springframework.web.servlet.ModelAndView;

import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;

public class PasswordResetControllerTest {
    @Rule
    public MockitoRule unit = MockitoJUnit.rule();
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private MailService mailService;
    @Mock
    private UserAdminService userAdminService;
    @Mock
    private UserService userService;
    @Mock
    private CrowdControl crowdControl;
    @InjectMocks
    private PasswordResetController passwordResetController;

    @Test
    public void testPasswordChangeRequestFailsWhenThereIsNoMatchingUser() {
        String user = "user";

        failUserLookup(user);

        ModelAndView modelAndView = passwordResetController.submitRequest(user);

        assertThat(modelAndView, view(PasswordResetController.RESET_PASSWORD_FORM)
                .withUser(user)
                .notFound());
    }

    @Test
    public void testPasswordChangeRequestFailsWhenPasswordIsReadOnly() {
        String user = "user";

        goodUserLookup(user);
        cantResetPassword(user);

        ModelAndView modelAndView = passwordResetController.submitRequest(user);

        assertThat(modelAndView, view(PasswordResetController.RESET_PASSWORD_FORM)
                .withUser(user)
                .notEditable());
    }

    @Test
    public void testPasswordChangeRequestFailsWhenMailNotConfigured() {
        String user = "user";

        goodUserLookup(user);
        canResetPassword(user);
        mailNotConfigured();

        ModelAndView modelAndView = passwordResetController.submitRequest(user);

        assertThat(modelAndView, view(PasswordResetController.RESET_PASSWORD_FORM)
                .withUser(user)
                .withError("bitbucket.web.user.password.reset.mail.error"));
    }

    @Test
    public void testPasswordChangeRequestFailsGracefullyOnGenericError() {
        String user = "user";
        String generalError = "general error";

        goodUserLookup(user);
        canResetPassword(user);
        genericError(generalError);

        ModelAndView modelAndView = passwordResetController.submitRequest(user);

        assertThat(modelAndView, view(PasswordResetController.RESET_PASSWORD_FORM)
                .withUser(user)
                .withError(generalError));
    }

    @Test
    public void testPasswordChangeRequestIsSent() {
        String user = "user";

        goodUserLookup(user);
        canResetPassword(user);

        ModelAndView modelAndView = passwordResetController.submitRequest(user);

        assertThat(modelAndView, view(PasswordResetController.RESET_PASSWORD_ACKNOWLEDGMENT));
        verify(userAdminService).requestPasswordReset(user);
    }

    private void failUserLookup(String user) {
        when(userService.findUserByNameOrEmail(user)).thenReturn(null);
    }

    private TestApplicationUser goodUserLookup(String user) {
        TestApplicationUser applicationUser = new TestApplicationUser(user);
        when(userService.findUserByNameOrEmail(user)).thenReturn(applicationUser);

        return applicationUser;
    }

    private void cantResetPassword(String user) {
        when(crowdControl.canResetPassword(user)).thenReturn(false);
    }

    private void canResetPassword(String user) {
        when(crowdControl.canResetPassword(user)).thenReturn(true);
    }

    private static PasswordModelAndViewMatcher view(String name) {
        return new PasswordModelAndViewMatcher(name);
    }

    private void mailNotConfigured() {
        doThrow(new MailException(new KeyedMessage("mail", "mail", "mail")))
                .when(userAdminService).requestPasswordReset(anyString());
    }

    private void genericError(String error) {
        doThrow(new RuntimeException(error))
                .when(userAdminService).requestPasswordReset(anyString());
    }

    private static class PasswordModelAndViewMatcher extends AbstractModelAndViewMatcher<PasswordModelAndViewMatcher> {

        private PasswordModelAndViewMatcher(String name) {
            super(name);
        }

        private PasswordModelAndViewMatcher withUser(String name) {
            return withEntry("identifier", name);
        }

        private PasswordModelAndViewMatcher notFound() {
            return withEntry("userNotEditable", false).withEntry("userNotFound", true);
        }

        private PasswordModelAndViewMatcher notEditable() {
            return withEntry("userNotEditable", true).withEntry("userNotFound", false);
        }

        private PasswordModelAndViewMatcher withError(String error) {
            return withEntry("userNotEditable", false)
                    .withEntry("userNotFound", false)
                    .withEntry("formErrors", Matchers.contains(error));
        }

        @Override
        protected PasswordModelAndViewMatcher self() {
            return this;
        }
    }
}