package cz.muni.fi.pa165.core.user;

import cz.muni.fi.pa165.core.house.House;
import cz.muni.fi.pa165.core.house.HouseService;
import cz.muni.fi.pa165.core.metrics.Metrics;
import cz.muni.fi.pa165.core.metrics.MetricsService;
import cz.muni.fi.pa165.core.smartmeter.SmartMeter;
import cz.muni.fi.pa165.core.smartmeter.SmartMeterService;
import cz.muni.fi.pa165.core.user.roles.HouseRole;
import cz.muni.fi.pa165.core.user.roles.RoleService;
import cz.muni.fi.pa165.model.dto.role.enums.HouseRoleEnum;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;

public class UserServiceTest {
    @InjectMocks
    private UserService userService;

    @Mock
    private MetricsService metricsService;

    @Mock
    private SmartMeterService smartMeterService;

    @Mock
    private HouseService houseService;

    @Mock
    private RoleService roleService;

    @Mock
    private UserRepository userReposiroyMock;

    @Mock
    private UserFacade userFacade;

    @BeforeEach
    void init() {
        openMocks(this);
    }

    @Test
    void shouldGetUserById() {

        User user = User
                .builder()
                .username("Test")
                .build();

        when(userReposiroyMock.findById(user.getId())).thenReturn(Optional.of(user));

        User result = userService.findById(user.getId());

        verify(userReposiroyMock).findById(user.getId());

        assertEquals(user, result);
    }

    @Test
    void shouldCreateNewDevice() {

        User user = User
                .builder()
                .username("Test")
                .build();

        when(userReposiroyMock.save(user)).thenReturn(user);

        User createdUser = userService.create(user);

        verify(userReposiroyMock).save(user);

        assertThat(createdUser).isEqualTo(user);
    }

    @Test
    void shouldFindUserByName() {

        User user = User
                .builder()
                .username("Test")
                .build();

        when(userReposiroyMock.findByUsername(user.getUsername())).thenReturn(Optional.of(user));

        User result = userService.findByUsername(user.getUsername());

        verify(userReposiroyMock).findByUsername(user.getUsername());

        assertEquals(user, result);
    }

    @Test
    void shouldFindAllUserPageableInt() {

        User user1 = User
                .builder()
                .username("Test")
                .build();

        User user2 = User
                .builder()
                .username("Test2")
                .build();

        Page<User> page = new PageImpl<>(List.of(user1, user2));

        int pageNumber = 0;
        int pageSize = 10;

        when(userReposiroyMock.findAll(PageRequest.of(pageNumber, pageSize))).thenReturn(page);

        Page<User> result = userService.findAllPageableInt(pageNumber);

        verify(userReposiroyMock).findAll(PageRequest.of(pageNumber, pageSize));

        assertThat(result).containsExactlyInAnyOrder(user1, user2);
    }

    @Test
    void shouldDeleteUserById() {

        User user = User
                .builder()
                .username("Test")
                .build();

        when(userReposiroyMock.findById(user.getId())).thenReturn(Optional.of(user));

        try {
            User deletedUser = userService.deleteById(user.getId());

            verify(userReposiroyMock).findById(user.getId());
            verify(userReposiroyMock).save(user); // soft delete

            assertNotNull(deletedUser.getDeletedAt());
            assertEquals(user, deletedUser);
        } catch (Exception e){
            Assertions.assertThat(false).isTrue();
        }
    }

    @Test
    void shouldUpdateUser() {

        User originalUser = User
                .builder()
                .username("Test1")
                .build();

        User updatedUser = User
                .builder()
                .username("Test2")
                .build();

        when(userReposiroyMock.findById(originalUser.getId())).thenReturn(Optional.of(originalUser));

        when(userReposiroyMock.save(updatedUser)).thenReturn(updatedUser);

        User result = userService.update(updatedUser, originalUser.getId());

        verify(userReposiroyMock).findById(originalUser.getId());
        verify(userReposiroyMock).save(updatedUser);

        assertEquals(originalUser.getId(), result.getId());
        assertEquals(updatedUser.getUsername(), result.getUsername());
    }

    @Test
    void shouldHardDeleteAllUsers() {
        userService.hardDeleteAll();
        verify(userReposiroyMock).deleteAll();
    }

    @Test
    void shouldDeleteAllUsers() {
        var data = List.of(
                User.builder().username("Test1").build(),
                User.builder().username("Test2").build()
        );
        when(userReposiroyMock.findAll()).thenReturn(data);
        userService.deleteAll();
        verify(userReposiroyMock).findAll();
        verify(userReposiroyMock).saveAll(data);
    }

    @Test
    void shouldDeleteAllUsersIds() {
        User firstUser = User
                .builder()
                .username("Test1")
                .build();

        User secondUser = User
                .builder()
                .username("Test2")
                .build();

        String[] ids = {firstUser.getId(), secondUser.getId()};
        when(userReposiroyMock.findById(ids[0])).thenReturn(Optional.of(firstUser));
        when(userReposiroyMock.findById(ids[1])).thenReturn(Optional.of(secondUser));

        try {
            userService.deleteAllById(ids);

            verify(userReposiroyMock).save(firstUser);
            verify(userReposiroyMock).save(secondUser);
        }
        catch (Exception e){
            Assertions.assertThat(false).isTrue();
        }
    }

    @Test
    void shouldFindAllUsers() {
        var data = List.of(
                User.builder().username("Test1").build(),
                User.builder().username("Test2").build()
        );

        when(userReposiroyMock.findAll()).thenReturn(data);
        List<User> result = userService.findAll();
        verify(userReposiroyMock).findAll();
        assertEquals(data, result);
    }

    @Test
    void shouldFindAllUsersPageable() {
        int pageNumber = 0;
        int pageSize = 10;
        Pageable pageable = PageRequest.of(pageNumber, pageSize);

        var data = List.of(
                User.builder().username("Test1").build(),
                User.builder().username("Test2").build()
        );

        Page<User> pageResult = new PageImpl<>(data, pageable, data.size());
        when(userReposiroyMock.findAll(pageable)).thenReturn(pageResult);

        Page<User> result = userService.findAllPageable(pageable);
        verify(userReposiroyMock).findAll(pageable);

        assertEquals(pageResult, result);
    }

    @Test
    void shouldReturnUserMonthConsumption() {
        User user =
                User.builder()
                        .email("test@gmail.com")
                        .firstName("lol")
                        .lastName("lol")
                        .username("lol")
                        .password("lol")
                        .userType(UserType.ADMIN)
                        .build();
        user.setId("6a9f853d-59ef-4636-9c0b-bbac44bfd7f4");
        userService.create(user);

        House house = House.builder()
                .address("lol")
                .city("lol")
                .state("lol")
                .zipcode("lol")
                .build();
        house.setId("b256ac78-dc47-11ed-afa1-0242ac120002");
        houseService.create(house);

        HouseRole role = HouseRole.builder()
                .houseRole(HouseRoleEnum.Owner)
                .house(house)
                .build();
        role.setUser(user);
        roleService.create(role);

        SmartMeter smartMeter = SmartMeter
                .builder()
                .house(house)
                .build();
        smartMeterService.create(smartMeter);


        double consumptionSeed = 0;
        Metrics metrics = Metrics.builder()
                .consumptionKWH(consumptionSeed)
                .smartMeter(smartMeter)
                .build();
        metricsService.create(metrics);

        double consumption = userService.getConsumption(user.getId(), house.getId(),
                Instant.parse("2023-01-01T00:00:00.00Z"),
                Instant.parse("2023-01-01T00:00:00.00Z"));

        assertEquals(consumptionSeed, consumption);
    }
}
