package alexp.blog.service;

import alexp.blog.model.Role;
import alexp.blog.model.User;
import alexp.blog.repository.RoleRepository;
import alexp.blog.repository.UserRepository;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;

public class UserServiceTest {

    public static final String NAME = "name";
    public static final String EMAIL = "email";

    @Mock
    private UserRepository userRepository;

    @Mock
    private RoleRepository roleRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks
    private UserServiceImpl userService;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void shouldFindByEmail() {
        User user = new User();

        when(userRepository.findByEmailIgnoreCase(EMAIL)).thenReturn(user);

        assertThat(userService.emailExists(EMAIL), is(equalTo(true)));

        assertThat(userService.findByEmail(EMAIL), is(equalTo(user)));

        verify(userRepository, atLeastOnce()).findByEmailIgnoreCase(Matchers.anyString());
    }

    @Test
    public void shouldReturnNullWhenEmailNotExists() {
        assertThat(userService.emailExists(EMAIL), is(equalTo(false)));

        assertThat(userService.findByEmail(EMAIL), is(equalTo(null)));

        verify(userRepository, atLeastOnce()).findByEmailIgnoreCase(Matchers.anyString());
    }

    @Test
    public void shouldFindByUsername() {
        User user = new User();

        when(userRepository.findByUsernameIgnoreCase(NAME)).thenReturn(user);

        assertThat(userService.usernameExists(NAME), is(equalTo(true)));

        assertThat(userService.findByUsername(NAME), is(equalTo(user)));

        verify(userRepository, atLeastOnce()).findByUsernameIgnoreCase(Matchers.anyString());
    }

    @Test
    public void shouldReturnNullWhenUsernameNotExists() {
        assertThat(userService.usernameExists(NAME), is(equalTo(false)));

        assertThat(userService.findByUsername(NAME), is(equalTo(null)));

        verify(userRepository, atLeastOnce()).findByUsernameIgnoreCase(Matchers.anyString());
    }

    @Test
    public void shouldAddNewUser() {
        Role role = new Role();

        when(roleRepository.findByName(Matchers.anyString())).thenReturn(role);

        User user = new User();

        userService.register(user);

        assertThat(user.isEnabled(), is(equalTo(true)));

        assertThat(user.getRoles().size(), is(equalTo(1)));
        assertThat(user.getRoles().get(0), is(role));

        assertThat(user.getRegistrationDate().toLocalDate().equals(LocalDate.now()), is(equalTo(true)));

        verify(userRepository, times(1)).saveAndFlush(user);

        verify(roleRepository, times(1)).findByName(Matchers.anyString());
    }

    @Test
    public void shouldEncodePassword() {
        String password = "password";
        String encodedPassword = "encodedPassword";

        User user = new User();
        user.setPassword(password);

        when(passwordEncoder.encode(password)).thenReturn(encodedPassword);

        userService.register(user);

        assertThat(user.getPassword(), is(equalTo(encodedPassword)));

        verify(passwordEncoder, times(1)).encode(password);
    }

    @Test
    public void shouldLoadUserDetails() {
        User user = new User();
        user.setUsername(NAME);
        user.setPassword("123");

        List<String> role1Names = Arrays.asList("role1", "role2");
        for (String roleName : role1Names) {
            Role role = new Role();
            role.setName(roleName);
            user.getRoles().add(role);
        }

        when(userRepository.findByUsernameOrEmail(NAME, NAME)).thenReturn(user);

        UserDetails userDetails = userService.loadUserByUsername(NAME);

        List<SimpleGrantedAuthority> authorities = role1Names.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        assertThat(userDetails.getAuthorities().containsAll(authorities), is(equalTo(true)));

        verify(userRepository, times(1)).findByUsernameOrEmail(NAME, NAME);
    }

    @Test(expected = UsernameNotFoundException.class)
    public void shouldThrowUsernameNotFound() {
        userService.loadUserByUsername("notExisting");
    }

}