package com.wishurgift.service;

import com.wishurgift.helper.UserMaker;
import com.wishurgift.helper.WishlistMaker;
import com.wishurgift.persistence.entity.User;
import com.wishurgift.persistence.entity.Wishlist;
import com.wishurgift.persistence.repository.UserRepository;
import com.wishurgift.persistence.repository.WishlistRepository;
import com.wishurgift.service.bean.UserServiceBean;
import com.wishurgift.service.dto.ErrorDTO;
import com.wishurgift.service.dto.LoginDTO;
import com.wishurgift.service.dto.UserDTO;
import com.wishurgift.service.dto.WishlistHeaderDTO;
import com.wishurgift.service.dto.request.LoginRequestDTO;
import com.wishurgift.service.dto.request.RegisterRequestDTO;
import com.wishurgift.service.dto.request.UpdateUserRequestDTO;
import com.wishurgift.service.dto.transformer.UserTransformer;
import com.wishurgift.service.dto.transformer.WishlistTransformer;
import com.wishurgift.utils.JwtUtils;
import com.wishurgift.utils.PasswordUtils;
import com.wishurgift.utils.StringUtils;
import com.wishurgift.wrapper.Page;
import com.wishurgift.wrapper.Result;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.junit.Assert.*;

public class UserServiceTest {

    @Mock
    UserRepository userRepository;

    @Mock
    WishlistRepository wishlistRepository;

    @Mock
    ContactService contactService;

    @InjectMocks
    UserServiceBean service;

    @Before
    public void before() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void loginTest() {
        LoginRequestDTO loginTry = UserMaker.makeLoginRequestDTO();
        User user = UserMaker.makeUser();
        user.setPassword(PasswordUtils.hash("pass"));

        Mockito.when(userRepository.findByEmail(loginTry.getLogin())).thenReturn(Optional.of(user));

        LoginDTO response = service.login(loginTry);
        assertEquals(user.getId(), response.getUser().getId());
        assertEquals(StringUtils.capitalize(user.getName()), response.getUser().getName());
        assertEquals(user.getEmail(), response.getUser().getEmail());
        assertEquals(UserTransformer.convertTag(user.getName(), user.getTag()), response.getUser().getTag());
        assertEquals(user.isPublic(), response.getUser().isPublic());
    }

    @Test
    public void loginNotExistTest() {
        LoginRequestDTO loginTry = UserMaker.makeLoginRequestDTO();

        Mockito.when(userRepository.findByEmail(loginTry.getLogin())).thenReturn(Optional.empty());

        LoginDTO response = service.login(loginTry);
        assertNull(response);
    }

    @Test
    public void loginPasswordIncorrectTest() {
        LoginRequestDTO loginTry = UserMaker.makeLoginRequestDTO();
        //on change pas le mot de pass qui est donc mauvais
        User user = UserMaker.makeUser();

        Mockito.when(userRepository.findByEmail(loginTry.getLogin())).thenReturn(Optional.of(user));

        LoginDTO response = service.login(loginTry);
        assertNull(response);
    }

    @Test
    public void registerTest() {
        RegisterRequestDTO register = UserMaker.makeRegisterRequestDTO();

        Mockito.when(userRepository.findByEmail(register.getEmail().toLowerCase())).thenReturn(Optional.empty());
        Mockito.when(userRepository.findByTag(Mockito.anyString())).thenReturn(Optional.empty());

        UserDTO actual = service.register(register);
        assertEquals(StringUtils.capitalize(register.getName()), actual.getName());
        assertEquals(register.getEmail(), actual.getEmail());
        assertTrue(StringUtils.isNotBlank(actual.getTag()));
        assertFalse(actual.isPublic());
    }

    @Test
    public void registerEmailExistTest() {
        User user = UserMaker.makeUser();
        RegisterRequestDTO register = UserMaker.makeRegisterRequestDTO();

        Mockito.when(userRepository.findByEmail(Mockito.anyString())).thenReturn(Optional.of(user));

        UserDTO actual = service.register(register);
        assertNull(actual);
    }

    @Test
    public void resfreshTest() throws InterruptedException {
        Principal principal = makePrincipal();
        String badToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.yylOzYIEju5WdXoRkaFUL252-xI5d43MCA5q1o742qU";
        String expiredToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6Im5hbWUiLCJlbWFpbCI6ImVtYWlsQGVtYWlsLmNvbSIsInRhZyI6Im5hbWUjMTIzNDUiLCJwdWJsaWMiOmZhbHNlLCJleHAiOjE1ODA4MjU0NDZ9.P793p7EvvHcXetVQw9avhEpWTDm4OpQXHo6rfQZbWOw";
        Thread.sleep(2000);

        String refresh = service.refresh(principal);
        assertNotNull(refresh);
        assertNotEquals(principal.getName(), refresh);
        assertTrue(JwtUtils.verify(principal.getName()));
        assertTrue(JwtUtils.verify(refresh));
        assertEquals(JwtUtils.getUser(principal.getName()), JwtUtils.getUser(refresh));

        principal = makePrincipal(expiredToken);
        refresh = service.refresh(principal);
        assertNull(refresh);

        principal = makePrincipal(badToken);
        refresh = service.refresh(principal);
        assertNull(refresh);
    }

    @Test
    public void getByIdTest() {
        User expected = UserMaker.makeUser();

        Mockito.when(userRepository.getById(expected.getId())).thenReturn(expected);

        UserDTO actual = service.getById(expected.getId());
        assertNotNull(actual);
        assertEquals(UserTransformer.entityToDto(expected), actual);
    }

    @Test
    public void notFoundGetByIdTest() {
        long id = 8;
        Mockito.when(userRepository.getById(id)).thenReturn(null);

        UserDTO actual = service.getById(id);
        assertNull(actual);
    }

    @Test
    public void userNotFoundGetByIdTest() {
        long id = 8;

        Mockito.when(userRepository.getById(id)).thenReturn(null);

        UserDTO actual = service.getById(id);
        assertNull(actual);
    }

    @Test
    public void getUserIsPublicTest() {
        User user = UserMaker.makeUser();
        user.setPublic(true);
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(user));

        Result<UserDTO> result = service.getUser(userDTO, id);
        assertTrue(result.isSuccess());
        UserDTO actual = result.getData();
        assertEquals(UserTransformer.entityToDto(user), actual);
    }

    @Test
    public void getUserSelfTest() {
        User user = UserMaker.makeUser();
        UserDTO userDTO = UserMaker.makeUserDTO();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));

        Result<UserDTO> result = service.getUser(userDTO, userDTO.getId());
        assertTrue(result.isSuccess());
        UserDTO actual = result.getData();
        assertEquals(UserTransformer.entityToDto(user), actual);
    }

    @Test
    public void getUserSelfAfterUpdateTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        User user = UserMaker.makeUser();
        user.setId(userDTO.getId());
        user.setName("NewName");
        user.setPublic(true);

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));

        Result<UserDTO> result = service.getUser(userDTO, userDTO.getId());
        assertTrue(result.isSuccess());
        assertEquals(UserTransformer.entityToDto(user), result.getData());
    }

    @Test
    public void getUserIsContactTest() {
        User user = UserMaker.makeUser();
        user.setPublic(false);
        UserDTO userDTO = UserMaker.makeUserDTO();
        userDTO.setId(2);
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(user));
        Mockito.when(contactService.isContact(userDTO, UserTransformer.entityToDto(user))).thenReturn(true);

        Result<UserDTO> result = service.getUser(userDTO, id);
        assertTrue(result.isSuccess());
        UserDTO actual = result.getData();
        assertEquals(UserTransformer.entityToDto(user), actual);
    }

    @Test
    public void getUser4NotConnectedTest() {
        User user = UserMaker.makeUser();
        user.setPublic(true);
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(user));

        Result<UserDTO> result = service.getUser(null, id);
        assertTrue(result.isSuccess());
        UserDTO actual = result.getData();
        assertEquals(UserTransformer.entityToDto(user), actual);
    }

    @Test
    public void getUserErrorTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.empty());

        Result<ErrorDTO> result = service.getUser(userDTO, id);
        assertFalse(result.isSuccess());
        ErrorDTO actual = result.getData();
        assertEquals(Result.Error.UNABLE_TO_GET_DATA.getCode(), actual.getCode());
        assertEquals(Result.Error.UNABLE_TO_GET_DATA.getMessage(), actual.getMessage());
    }

    @Test
    public void getUserError2Test() {
        User user = UserMaker.makeUser();
        user.setPublic(false);
        UserDTO userDTO = UserMaker.makeUserDTO();
        userDTO.setId(2);
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(user));
        Mockito.when(contactService.isContact(userDTO, UserTransformer.entityToDto(user))).thenReturn(false);

        Result<ErrorDTO> result = service.getUser(userDTO, id);
        assertFalse(result.isSuccess());
        ErrorDTO actual = result.getData();
        assertEquals(Result.Error.UNAUTHORIZED.getCode(), actual.getCode());
        assertEquals(Result.Error.UNAUTHORIZED.getMessage(), actual.getMessage());
    }

    @Test
    public void getUserError3Test() {
        User user = UserMaker.makeUser();
        user.setPublic(false);
        long id = 8;

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(user));

        Result<ErrorDTO> result = service.getUser(null, id);
        assertFalse(result.isSuccess());
        ErrorDTO actual = result.getData();
        assertEquals(Result.Error.UNAUTHORIZED.getCode(), actual.getCode());
        assertEquals(Result.Error.UNAUTHORIZED.getMessage(), actual.getMessage());
    }

    @Test
    public void searchTest() {
        String request = "search";
        int limit = 8;
        List<User> expected = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            User user = UserMaker.makeUser();
            user.setId(i + 1);
            expected.add(user);
        }

        Mockito.when(userRepository.search(request, limit)).thenReturn(expected);

        Page<UserDTO> actual = service.search(request, limit);
        assertEquals(expected.size(), actual.getTotal());
        assertEquals(UserTransformer.entityToDto(expected), actual.getList());
    }

    @Test
    public void updateTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        UpdateUserRequestDTO request = UserMaker.makeUpdateUserRequestDTO();
        User user = UserMaker.makeUser();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findByEmail(request.getEmail())).thenReturn(Optional.empty());

        UserDTO actual = service.update(userDTO, request);
        assertEquals(request.getEmail(), actual.getEmail());
        assertEquals(StringUtils.capitalize(request.getName()), actual.getName());
        assertEquals(request.isPublic(), actual.isPublic());
    }

    @Test
    public void unableToFindUserUpdateTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        UpdateUserRequestDTO request = UserMaker.makeUpdateUserRequestDTO();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.empty());

        UserDTO actual = service.update(userDTO, request);
        assertNull(actual);
    }

    @Test
    public void emailAlreadyUsedUpdateTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        UpdateUserRequestDTO request = UserMaker.makeUpdateUserRequestDTO();
        User user = UserMaker.makeUser();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findByEmail(request.getEmail())).thenReturn(Optional.of(user));

        UserDTO actual = service.update(userDTO, request);
        assertNull(actual);
    }

    @Test
    public void getWishlistTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;
        User user = UserMaker.makeUser();
        User owner = UserMaker.makeUser();
        owner.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(owner));
        Mockito.when(wishlistRepository.getAllByUser(owner, user)).thenReturn(expected);

        Page<WishlistHeaderDTO> actual = service.getWishlist(userDTO, id);
        assertNull(actual.getSize());
        assertNull(actual.getPage());
        assertEquals(expected.size(), actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void getWishlistSameUserTest() {
        long id = 8;
        UserDTO userDTO = UserMaker.makeUserDTO();
        userDTO.setId(id);
        User user = UserMaker.makeUser();
        user.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(wishlistRepository.getAllByUser(user, user)).thenReturn(expected);

        Page<WishlistHeaderDTO> actual = service.getWishlist(userDTO, id);
        assertNull(actual.getSize());
        assertNull(actual.getPage());
        assertEquals(expected.size(), actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void getWishlistNullUserTest() {
        long id = 8;
        User owner = UserMaker.makeUser();
        owner.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(owner));
        Mockito.when(wishlistRepository.getAllByUser(owner, null)).thenReturn(expected);

        Page<WishlistHeaderDTO> actual = service.getWishlist(null, id);
        assertNull(actual.getSize());
        assertNull(actual.getPage());
        assertEquals(expected.size(), actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void noOwnerGetWishlistTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;
        User user = UserMaker.makeUser();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findById(id)).thenReturn(Optional.empty());

        Page<WishlistHeaderDTO> actual = service.getWishlist(userDTO, id);
        assertNull(actual);
    }


    @Test
    public void getWishlistPageTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;
        int page = 1;
        int size = 5;
        long total = 8;
        User user = UserMaker.makeUser();
        User owner = UserMaker.makeUser();
        owner.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(owner));
        Mockito.when(wishlistRepository.getAllByUser(page, size, owner, user)).thenReturn(expected);
        Mockito.when(wishlistRepository.countAllByUser(owner, user)).thenReturn(total);

        Page<WishlistHeaderDTO> actual = service.getWishlist(page, size, userDTO, id);
        assertEquals(expected.size(), (long) actual.getSize());
        assertEquals(page, (long) actual.getPage());
        assertEquals(total, actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void getWishlistPageSameUserTest() {
        long id = 8;
        int page = 1;
        int size = 5;
        long total = 8;
        UserDTO userDTO = UserMaker.makeUserDTO();
        userDTO.setId(id);
        User user = UserMaker.makeUser();
        user.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(wishlistRepository.getAllByUser(page, size, user, user)).thenReturn(expected);
        Mockito.when(wishlistRepository.countAllByUser(user, user)).thenReturn(total);

        Page<WishlistHeaderDTO> actual = service.getWishlist(page, size, userDTO, id);
        assertEquals(expected.size(), (long) actual.getSize());
        assertEquals(page, (long) actual.getPage());
        assertEquals(total, actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void getWishlistPageNullUserTest() {
        long id = 8;
        int page = 1;
        int size = 5;
        long total = 8;
        User owner = UserMaker.makeUser();
        owner.setId(id);
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);

        Mockito.when(userRepository.findById(id)).thenReturn(Optional.of(owner));
        Mockito.when(wishlistRepository.getAllByUser(page, size, owner, null)).thenReturn(expected);
        Mockito.when(wishlistRepository.countAllByUser(owner, null)).thenReturn(total);

        Page<WishlistHeaderDTO> actual = service.getWishlist(page, size, null, id);
        assertEquals(expected.size(), (long) actual.getSize());
        assertEquals(page, (long) actual.getPage());
        assertEquals(total, actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt))));
    }

    @Test
    public void noOwnerGetWishlistPageTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        long id = 8;
        int page = 1;
        int size = 5;
        User user = UserMaker.makeUser();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(userRepository.findById(id)).thenReturn(Optional.empty());

        Page<WishlistHeaderDTO> actual = service.getWishlist(page, size, userDTO, id);
        assertNull(actual);
    }

    @Test
    public void getSharedTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();
        User user = UserMaker.makeUser();
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);
        expected.get(1).setName("otherName");

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(wishlistRepository.getShared(user)).thenReturn(expected);

        Page<WishlistHeaderDTO> actual = service.getShared(userDTO);
        assertEquals(expected.size(), actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt, true))));
    }

    @Test
    public void userNotFoundetSharedTest() {
        UserDTO userDTO = UserMaker.makeUserDTO();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.empty());

        Page<WishlistHeaderDTO> actual = service.getShared(userDTO);
        assertNull(actual);
    }

    @Test
    public void getSharedPageTest() {
        int page = 1;
        int size = 5;
        long total = 8;
        UserDTO userDTO = UserMaker.makeUserDTO();
        User user = UserMaker.makeUser();
        List<Wishlist> expected = new ArrayList<>();
        expected.add(WishlistMaker.makeWishlist());
        expected.add(WishlistMaker.makeWishlist());
        expected.get(1).setId(2);
        expected.get(1).setName("otherName");

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.of(user));
        Mockito.when(wishlistRepository.getShared(page, size, user)).thenReturn(expected);
        Mockito.when(wishlistRepository.countShared(user)).thenReturn(total);

        Page<WishlistHeaderDTO> actual = service.getShared(page, size, userDTO);
        assertEquals(expected.size(), (long) actual.getSize());
        assertEquals(page, (long) actual.getPage());
        assertEquals(total, actual.getTotal());
        expected.forEach(elt -> assertTrue(actual.getList().contains(WishlistTransformer.entityToHeaderDto(elt, true))));
    }

    @Test
    public void userNotFoundetSharedPageTest() {
        int page = 1;
        int size = 5;
        UserDTO userDTO = UserMaker.makeUserDTO();

        Mockito.when(userRepository.findById(userDTO.getId())).thenReturn(Optional.empty());

        Page<WishlistHeaderDTO> actual = service.getShared(page, size, userDTO);
        assertNull(actual);
    }

    private Principal makePrincipal() {
        return makePrincipal(JwtUtils.generate(UserMaker.makeUserDTO()));
    }

    private Principal makePrincipal(String name) {
        return new Principal() {
            @Override
            public String getName() {
                return name;
            }
        };
    }

}
