package br.com.rayankonecny.userserviceapi.service;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import br.com.rayankonecny.userserviceapi.entity.User;
import br.com.rayankonecny.userserviceapi.mapper.UserMapper;
import br.com.rayankonecny.userserviceapi.repository.UserRepository;
import models.exceptions.ResourceNotFoundException;
import models.requests.CreateUserRequest;
import models.requests.UpdateUserRequest;
import models.responses.UserResponse;

import static br.com.rayankonecny.userserviceapi.creator.CreatorUtils.generateMock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;

@SpringBootTest
public class UserServiceTest {

  @InjectMocks
  private UserService service;

  @Mock
  private UserRepository repository;

  @Mock
  private UserMapper mapper;

  @Mock
  private BCryptPasswordEncoder encoder;

  @Test
  public void whenCallFindByIdWithValidIdTheReturnUserResponse() {
    when(repository.findById(anyString())).thenReturn(Optional.of(new User()));
    when(mapper.fromEntity(any(User.class))).thenReturn(generateMock(UserResponse.class));

    final var response = service.findById("1");

    Assertions.assertNotNull(response);
    assertEquals(UserResponse.class, response.getClass());

    verify(repository).findById(anyString());
    verify(mapper).fromEntity(any(User.class));
  }

  @Test
  public void whenCallFindByIdWithInvalidIdThenThrowResourceNotFoundException() {
    when(repository.findById(anyString())).thenReturn(Optional.empty());

    try {
      service.findById("1");
    } catch (Exception ex) {
      assertEquals(ResourceNotFoundException.class, ex.getClass());
      assertEquals("Object not found! Id: 1, Type: UserResponse", ex.getMessage());
    }

    verify(repository, times(1)).findById(anyString());
    verify(mapper, times(0)).fromEntity(any(User.class));
  }

  @Test
  public void whenCallFindAllThenReturnListOfUserResponses() {
    // Implement test for findAll method
    when(repository.findAll()).thenReturn(List.of(new User(), new User()));
    when(mapper.fromEntity(any(User.class))).thenReturn(generateMock(UserResponse.class));

    final var responses = service.findAll();

    assertNotNull(responses);
    assertEquals(2, responses.size());
    assertEquals(UserResponse.class, responses.get(0).getClass());

    verify(repository, times(1)).findAll();
    verify(mapper, times(2)).fromEntity(any(User.class));

  }

  @Test
  void whenCallSaveThenSucess() {

    final var request = generateMock(CreateUserRequest.class);

    when(mapper.fromRequest(any())).thenReturn(new User());
    when(encoder.encode(anyString())).thenReturn("encoded");
    when(repository.save(any(User.class))).thenReturn(new User());
    when(repository.findByEmail(anyString())).thenReturn(Optional.empty());

    service.save(request);

    verify(mapper).fromRequest(request);
    verify(encoder).encode(request.password());
    verify(repository).save(any(User.class));
    verify(repository).findByEmail(request.email());

  }

  @Test
  void whenCallSaveWithInvalidEmailThenThrowDataIntegrityViolationException() {

    final var request = generateMock(CreateUserRequest.class);
    final var entity = generateMock(User.class);

    when(repository.findById(anyString())).thenReturn(Optional.of(entity));
    when(repository.findByEmail(anyString())).thenReturn(Optional.of(entity));

    try {
      service.save(request);
    } catch (Exception ex) {
      assertEquals(DataIntegrityViolationException.class, ex.getClass());
      assertEquals("Email [" + request.email() + "] already exists", ex.getMessage());
    }

    verify(repository).findByEmail(request.email());
    verify(mapper, times(0)).fromRequest(any());
    verify(encoder, times(0)).encode(anyString());
    verify(repository, times(0)).save(any(User.class));

  }

  @Test
  void whenCallUpdateWithValidParamsThenGetSuccess() {
    final var id = "1";
    final var request = generateMock(UpdateUserRequest.class);
    final var entity = generateMock(User.class).withId(id);

    when(repository.findById(anyString())).thenReturn(Optional.of(entity));
    when(repository.findByEmail(anyString())).thenReturn(Optional.empty());
    when(mapper.update(any(), any())).thenReturn(entity);
    when(repository.save(any(User.class))).thenReturn(entity);

    service.update(id, request);

    verify(repository).findById(anyString());
    verify(repository).findByEmail(request.email());
    verify(mapper).update(request, entity);
    verify(encoder).encode(request.password());
    verify(repository).save(any(User.class));
    verify(mapper).fromEntity(any(User.class));
  }

}
