package gov.samhsa.consent2share.web.controller;

import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import gov.samhsa.consent2share.domain.account.TroubleType;
import gov.samhsa.consent2share.infrastructure.security.EmailAddressNotExistException;
import gov.samhsa.consent2share.infrastructure.security.TokenExpiredException;
import gov.samhsa.consent2share.infrastructure.security.TokenNotExistException;
import gov.samhsa.consent2share.infrastructure.security.UsernameNotExistException;
import gov.samhsa.consent2share.service.account.AccountService;
import gov.samhsa.consent2share.service.account.PasswordResetService;
import gov.samhsa.consent2share.service.dto.LoginTroubleDto;
import gov.samhsa.consent2share.service.dto.PasswordResetDto;
import gov.samhsa.consent2share.service.validator.FieldValidatorChangePassword;
import gov.samhsa.consent2share.service.validator.FieldValidatorLoginTroubleCreateNewPassword;
import gov.samhsa.consent2share.service.validator.FieldValidatorLoginTroublePassword;
import gov.samhsa.consent2share.service.validator.FieldValidatorLoginTroubleSelection;
import gov.samhsa.consent2share.service.validator.FieldValidatorLoginTroubleUsername;
import gov.samhsa.consent2share.web.controller.AccountController;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.util.StringUtils;

public class AccountControllerTest {

	private AccountController sut;

	private FieldValidatorLoginTroublePassword fieldValidatorPassword;
	private FieldValidatorLoginTroubleUsername fieldValidatorUsername;
	private FieldValidatorLoginTroubleSelection fieldValidatorTroubleSelection;
	private FieldValidatorLoginTroubleCreateNewPassword fieldValidatorLoginTroubleCreateNewPassword;
	private PasswordResetService passwordResetService;
	private AccountService accountService;
	private FieldValidatorChangePassword fieldValidatorChangePassword;

	private MockMvc mockMvc;

	@Before
	public void setUp() {
		// Mock dependencies and create sut
		// Just to save a few lines of code for each individual test
		// But independency, clarity of the unit tests are much more important
		// than code reuse
		passwordResetService = mock(PasswordResetService.class);
		accountService = mock(AccountService.class);

		fieldValidatorPassword = new FieldValidatorLoginTroublePassword();
		fieldValidatorUsername = new FieldValidatorLoginTroubleUsername();
		
		fieldValidatorTroubleSelection = new FieldValidatorLoginTroubleSelection();
		fieldValidatorLoginTroubleCreateNewPassword = new FieldValidatorLoginTroubleCreateNewPassword();

		sut = new AccountController(fieldValidatorPassword,
				fieldValidatorLoginTroubleCreateNewPassword,
				fieldValidatorTroubleSelection, fieldValidatorChangePassword, fieldValidatorUsername, passwordResetService, accountService);

		mockMvc = standaloneSetup(this.sut).build();
	}

	@Test
	public void testLoginTrouble_When_HttpGet() throws Exception {
		Matcher<LoginTroubleDto> loginTroubleDtoMatcher = notNullValue(LoginTroubleDto.class);

		mockMvc.perform(get("/loginTrouble.html"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeExists(
								StringUtils.uncapitalize(LoginTroubleDto.class
										.getSimpleName())))
				.andExpect(
						model().attribute(
								StringUtils.uncapitalize(LoginTroubleDto.class
										.getSimpleName()),
								loginTroubleDtoMatcher))
				.andExpect(model().hasNoErrors())
				.andExpect(view().name("views/loginTrouble"))
				.andDo(MockMvcResultHandlers.print());
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_No_Value_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", ""))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTrouble"));
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_0_Value_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", "0"))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTrouble"));
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_4_Value_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", "4"))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTrouble"));
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_Value_1_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", "1"))
				.andExpect(status().is(302))
				.andExpect(view().name("redirect:/loginTroublePassword.html"));
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_Value_2_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", "2"))
				.andExpect(status().is(302))
				.andExpect(view().name("redirect:/loginTroubleUsername.html"));
	}

	@Test
	public void testLoginTrouble_When_HttpPost_With_Value_3_For_Param_TroubleTypeId()
			throws Exception {
		mockMvc.perform(post("/loginTrouble.html").param("troubleTypeId", "3"))
				.andExpect(status().is(302))
				.andExpect(view().name("redirect:/loginTroubleOther.html"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpGet() throws Exception {

		Matcher<LoginTroubleDto> loginTroubleDtoMatcher = new TypeSafeMatcher<LoginTroubleDto>() {

			@Override
			public void describeTo(Description arg0) {
				arg0.appendText(String.format(
						"TroubleType of %s is UNKNOWN_PASSWORD",
						LoginTroubleDto.class.getSimpleName()));
			}

			@Override
			protected boolean matchesSafely(LoginTroubleDto item) {
				if (item != null
						&& item.getTroubleTypeId() == TroubleType.UNKNOWN_PASSWORD
								.getValue()) {
					return true;
				}
				return false;
			}
		};

		final String loginTroubleDtoDtoObjectName = StringUtils
				.uncapitalize(LoginTroubleDto.class.getSimpleName());

		mockMvc.perform(get("/loginTroublePassword.html"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeExists(loginTroubleDtoDtoObjectName))
				.andExpect(
						model().attribute(loginTroubleDtoDtoObjectName,
								loginTroubleDtoMatcher))
				.andExpect(model().hasNoErrors())
				.andExpect(view().name("views/loginTroublePassword"))
				.andDo(MockMvcResultHandlers.print());
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Value_WhiteSpace_For_Param_Username()
			throws Exception {

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username", " "))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Invalid_Lenght_For_Param_Username()
			throws Exception {

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username", "1"))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTroublePassword"));

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username",
						"1234567890123456789012345678901"))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Value_WhiteSpace_For_Param_Email()
			throws Exception {

		mockMvc.perform(post("/loginTroublePassword.html").param("email", " "))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Invalid_Value_For_Param_Email()
			throws Exception {

		mockMvc.perform(
				post("/loginTroublePassword.html").param("email", "blabal"))
				.andExpect(status().isOk()).andExpect(model().hasErrors())
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Valid_Values_For_Param_Username_And_Email()
			throws Exception {

		doNothing().when(passwordResetService).createPasswordResetToken(
				anyString(), anyString(), anyString());

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username", "blabal")
						.param("email", "test@test.com"))
				.andExpect(status().is(302))
				.andExpect(model().hasNoErrors())
				.andExpect(
						request().sessionAttribute("tokenMessage",
								"tokenSuccess"))
				.andExpect(view().name("redirect:/newPasswordRequested.html"));

		verify(passwordResetService, times(1)).createPasswordResetToken(
				anyString(), anyString(), anyString());
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Valid_Values_For_Param_Username_And_Email_And_When_With_UsernameNotExistException()
			throws Exception {

		doThrow(new UsernameNotExistException(""))
				.when(passwordResetService)
				.createPasswordResetToken(anyString(), anyString(), anyString());

		final String loginTroubleDtoObjectName = StringUtils
				.uncapitalize(LoginTroubleDto.class.getSimpleName());

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username", "blabal")
						.param("email", "test@test.com"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								loginTroubleDtoObjectName, "username"))
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testLoginTroublePassword_When_HttpPost_With_Valid_Values_For_Param_Username_And_Email_And_When_With_EmailAddressNotExistException()
			throws Exception {

		doThrow(new EmailAddressNotExistException("")).when(
				passwordResetService).createPasswordResetToken(anyString(),
				anyString(), anyString());

		final String loginTroubleDtoObjectName = StringUtils
				.uncapitalize(LoginTroubleDto.class.getSimpleName());

		mockMvc.perform(
				post("/loginTroublePassword.html").param("username", "blabal")
						.param("email", "test@test.com"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								loginTroubleDtoObjectName, "email"))
				.andExpect(view().name("views/loginTroublePassword"));
	}

	@Test
	public void testNewPasswordRequested_When_HttpGet() throws Exception {

		final String tokenMessageValue = "bla";

		mockMvc.perform(
				get("/newPasswordRequested.html").sessionAttr("tokenMessage",
						tokenMessageValue))
				.andExpect(status().isOk())
				.andExpect(model().attribute("tokenMessage", tokenMessageValue))
				.andExpect(model().hasNoErrors())
				.andExpect(view().name("views/newPasswordRequested"))
				.andDo(MockMvcResultHandlers.print());
	}

	@Test
	public void testVerifyLink_Returns_Bad_Request_When_HttpGet_Without_Para_Token()
			throws Exception {

		mockMvc.perform(get("/resetPasswordLink.html")).andExpect(
				status().is(400));
	}

	@Test
	public void testVerifyLink_When_HttpGet_With_Para_Token_And_When_Token_Expired()
			throws Exception {

		final String tokenVaule = "bla";

		when(passwordResetService.isPasswordResetTokenExpired(tokenVaule))
				.thenReturn(true);

		mockMvc.perform(
				get("/resetPasswordLink.html").param("token", tokenVaule))
				.andExpect(status().is(302))
				.andExpect(
						request().sessionAttribute("tokenMessage",
								"tokenExpired"))
				.andExpect(view().name("redirect:/newPasswordRequested.html"));
	}

	@Test
	public void testVerifyLink_When_HttpGet_With_Para_Token_And_When_Token_Not_Expired()
			throws Exception {

		final String tokenVaule = "bla";

		when(passwordResetService.isPasswordResetTokenExpired(tokenVaule))
				.thenReturn(false);

		mockMvc.perform(
				get("/resetPasswordLink.html").param("token", tokenVaule))
				.andExpect(status().is(302))
				.andExpect(request().sessionAttribute("token", tokenVaule))
				.andExpect(view().name("redirect:/createPassword.html"));
	}

	@Test
	public void testVerifyLink_When_HttpGet_With_Para_Token_And_When_Token_Not_Exist()
			throws Exception {

		final String tokenVaule = "bla";

		when(passwordResetService.isPasswordResetTokenExpired(tokenVaule))
				.thenThrow(new TokenNotExistException(""));

		mockMvc.perform(
				get("/resetPasswordLink.html").param("token", tokenVaule))
				.andExpect(status().is(302))
				.andExpect(
						request().sessionAttribute("tokenMessage",
								"tokenNotExist"))
				.andExpect(view().name("redirect:/newPasswordRequested.html"));
	}

	@Test
	public void testCreatePassword_When_HttpGet() throws Exception {

		final String tokenValue = "Bla";

		Matcher<PasswordResetDto> passwordResetDtoDtoMatcher = new TypeSafeMatcher<PasswordResetDto>() {

			@Override
			public void describeTo(Description arg0) {
				arg0.appendText(String.format("Token of %s is %s",
						PasswordResetDto.class.getSimpleName(), tokenValue));
			}

			@Override
			protected boolean matchesSafely(PasswordResetDto item) {
				if (item != null && item.getToken().equals(tokenValue)) {
					return true;
				}
				return false;
			}
		};

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(
				get("/createPassword.html").sessionAttr("token", tokenValue))
				.andExpect(status().isOk())
				.andExpect(model().attributeExists(passwordResetDtoObjectName))
				.andExpect(
						model().attribute(passwordResetDtoObjectName,
								passwordResetDtoDtoMatcher))
				.andExpect(model().hasNoErrors())
				.andExpect(view().name("views/createPassword"))
				.andDo(MockMvcResultHandlers.print());
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Value_WhiteSpace_For_Param_Password()
			throws Exception {

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(post("/createPassword.html").param("password", " "))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "password"))
				.andExpect(view().name("views/createPassword"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Invalid_Length_For_Param_Password()
			throws Exception {

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(
				post("/createPassword.html").param("password", "1234567"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "password"))
				.andExpect(view().name("views/createPassword"));

		mockMvc.perform(
				post("/createPassword.html").param("password",
						"1234567890123456789012345678901"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "password"))
				.andExpect(view().name("views/createPassword"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Value_WhiteSpace_For_Param_RepeatPassword()
			throws Exception {

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(
				post("/createPassword.html").param("password", "12345678")
						.param("repeatPassword", " "))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "repeatPassword"))
				.andExpect(view().name("views/createPassword"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Invalid_Length_For_Param_RepeatPassword()
			throws Exception {

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(
				post("/createPassword.html").param("password", "12345678")
						.param("repeatPassword", "1234567"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "repeatPassword"))
				.andExpect(view().name("views/createPassword"));

		mockMvc.perform(
				post("/createPassword.html").param("password", "12345678")
						.param("repeatPassword",
								"1234567890123456789012345678901"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "repeatPassword"))
				.andExpect(view().name("views/createPassword"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Different_Param_Password_And_RepeatPassword()
			throws Exception {

		final String passwordResetDtoObjectName = StringUtils
				.uncapitalize(PasswordResetDto.class.getSimpleName());

		mockMvc.perform(
				post("/createPassword.html").param("password", "12345678")
						.param("repeatPassword", "123456789"))
				.andExpect(status().isOk())
				.andExpect(
						model().attributeHasFieldErrors(
								passwordResetDtoObjectName, "password"))
				.andExpect(view().name("views/createPassword"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Valid_Param_Password_And_RepeatPassword()
			throws Exception {

		final String password = "Aa2345678$";

		mockMvc.perform(
				post("/createPassword.html").param("password", password).param(
						"repeatPassword", password))
				.andExpect(status().is(302))
				.andExpect(view().name("redirect:/accountUpdated.html"));
	}

	@Test
	public void tesCcreatePasswordk_When_HttpPost_With_Valid_Param_Password_And_RepeatPassword_And_Token_Expired()
			throws Exception {

		final String password = "Aa2345678$";

		doThrow(new TokenExpiredException(""))
				.when(passwordResetService)
				.resetPassword(Mockito.any(PasswordResetDto.class), anyString());

		mockMvc.perform(
				post("/createPassword.html").param("password", password).param(
						"repeatPassword", password))
				.andExpect(status().is(302))
				.andExpect(
						request().sessionAttribute("tokenMessage",
								"tokenExpired"))
				.andExpect(view().name("redirect:/newPasswordRequested.html"));
	}

	@Test
	public void testAccountUpdated_When_HttpGet() throws Exception {

		mockMvc.perform(get("/accountUpdated.html")).andExpect(status().isOk())
				.andExpect(view().name("views/accountUpdated"));
	}
	
	@Test
	public void testAccount_Username_Found() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn("test");
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "Albert")
				.param("lastname", "Smith")
				.param("birthdate", "1983-02-29")
				.param("email", "email@email.com"))
				.andExpect(status().isOk())
				.andExpect(model().hasNoErrors())
				.andExpect(view().name("views/loginTroubleUsernameFound"))
				.andDo(MockMvcResultHandlers.print());
	}
	
	@Test
	public void testAccount_Username_NotFound() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn(null);
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "Fake")
				.param("lastname", "Smith")
				.param("birthdate", "1983-02-29")
				.param("email", "email@email.com"))
				.andExpect(status().isOk())
				.andExpect(view().name("views/loginTroubleUsername"))
				.andExpect(model().hasErrors())
				.andDo(MockMvcResultHandlers.print());
	}
	
	@Test
	public void testAccount_Username_FirstNameError() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn("test");
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "a")
				.param("lastname", "Smith")
				.param("birthdate", "1983-02-29")
				.param("email", "email@email.com"))
				.andExpect(status().isOk())
				.andExpect(view().name("views/loginTroubleUsername"))
				.andExpect(model().hasErrors())
				.andDo(MockMvcResultHandlers.print());
	}
	
	@Test
	public void testAccount_Username_LastNameError() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn("test");
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "Albert")
				.param("lastname", "a")
				.param("birthdate", "1983-02-29")
				.param("email", "email@email.com"))
				.andExpect(status().isOk())
				.andExpect(view().name("views/loginTroubleUsername"))
				.andExpect(model().hasErrors())
				.andDo(MockMvcResultHandlers.print());
	}
	
	@Test
	public void testAccount_Username_BirthDateError() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn("test");
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "Albert")
				.param("lastname", "Smith")
				.param("birthdate", "")
				.param("email", "email@email.com"))
				.andExpect(status().isOk())
				.andExpect(view().name("views/loginTroubleUsername"))
				.andExpect(model().hasErrors())
				.andDo(MockMvcResultHandlers.print());
	}
	
	@Test
	public void testAccount_Username_EmailError() throws Exception {
		when(accountService.recoverUsername(anyString(), anyString(), anyString(), anyString())).thenReturn("test");
		mockMvc.perform(post("/loginTroubleUsername.html")
				.param("firstname", "Albert")
				.param("lastname", "Smith")
				.param("birthdate", "1983-02-29")
				.param("email", "wrongEmail"))
				.andExpect(status().isOk())
				.andExpect(view().name("views/loginTroubleUsername"))
				.andExpect(model().hasErrors())
				.andDo(MockMvcResultHandlers.print());
	}
}
