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

import cz.muni.fi.pa165.core.company.Company;
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.util.List;
import java.util.Optional;

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

/**
 * DeviceService unit tests
 *
 * @author xskacel
 */

public class DeviceServiceTest {

	@InjectMocks
	private DeviceService deviceService;

	@Mock
	private DeviceRepository deviceRepositoryMock;

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

	@Test
	void shouldGetDeviceById() {
		Company company = Company.builder().name("Apple").build();

		Device device = Device
				.builder()
				.company(company)
				.name("device-1")
				.build();

		when(deviceRepositoryMock.findById(device.getId())).thenReturn(Optional.of(device));

		Device result = deviceService.findById(device.getId());

		verify(deviceRepositoryMock).findById(device.getId());

		assertEquals(device, result);
	}

	@Test
	void shouldCreateNewDevice() {
		Company company = Company.builder().name("Apple").build();

		Device device = Device
				.builder()
				.company(company)
				.name("device-1")
				.build();

		when(deviceRepositoryMock.save(device)).thenReturn(device);

		Device createdDevice = deviceService.create(device);

		verify(deviceRepositoryMock).save(device);

		assertThat(createdDevice).isEqualTo(device);
	}

	@Test
	void shouldFindDeviceByName() {
		Company company = Company.builder().name("Apple").build();

		Device device = Device
				.builder()
				.company(company)
				.name("device-1")
				.build();

		when(deviceRepositoryMock.findByName(device.getName())).thenReturn(Optional.of(device));

		Device result = deviceService.findByName(device.getName());

		verify(deviceRepositoryMock).findByName(device.getName());

		assertEquals(device, result);
	}

	@Test
	void shouldFindAllDevicePageableInt() {
		Company company = Company.builder().name("Apple").build();

		Device firstDevice = Device.builder()
				.name("Bohemia")
				.company(company)
				.build();

		Device secondDevice = Device.builder()
				.name("New Company")
				.company(company)
				.build();

		Page<Device> page = new PageImpl<>(List.of(firstDevice, secondDevice));

		int pageNumber = 0;
		int pageSize = 10;

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

		Page<Device> result = deviceService.findAllPageableInt(pageNumber);

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

		assertThat(result).containsExactlyInAnyOrder(firstDevice, secondDevice);
	}

	@Test
	void shouldDeleteDeviceById() {
		Company company = Company.builder().name("Apple").build();

		Device device = Device.builder()
				.name("Bohemia")
				.company(company)
				.build();

		when(deviceRepositoryMock.findById(device.getId())).thenReturn(Optional.of(device));
	try {
		Device deletedDevice = deviceService.deleteById(device.getId());

		verify(deviceRepositoryMock).findById(device.getId());
		verify(deviceRepositoryMock).save(device); // soft delete

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

	@Test
	void shouldUpdateDevice() {
		Company firstCompany = Company.builder().name("Apple").build();
		Company secondCompany = Company.builder().name("New Company").build();

		Device originalDevice = Device.builder()
				.name("Bohemia")
				.company(firstCompany)
				.build();

		Device updatedDevice = Device.builder()
				.name("New Device")
				.company(secondCompany)
				.build();

		when(deviceRepositoryMock.findById(originalDevice.getId())).thenReturn(Optional.of(originalDevice));

		when(deviceRepositoryMock.save(updatedDevice)).thenReturn(updatedDevice);

		Device result = deviceService.update(updatedDevice, originalDevice.getId());

		verify(deviceRepositoryMock).findById(originalDevice.getId());
		verify(deviceRepositoryMock).save(updatedDevice);

		assertEquals(originalDevice.getId(), result.getId());
		assertEquals(updatedDevice.getName(), result.getName());
		assertEquals(updatedDevice.getCompany().getName(), result.getCompany().getName());
	}

	@Test
	void shouldHardDeleteAllDevices() {
		deviceService.hardDeleteAll();
		verify(deviceRepositoryMock).deleteAll();
	}

	@Test
	void shouldDeleteAllDevices() {
		var data = List.of(
				Device.builder().name("Bohemia").company(Company.builder().name("Apple").build()).build(),
				Device.builder().name("Slovakia").company(Company.builder().name("Mac").build()).build()
		);
		when(deviceRepositoryMock.findAll()).thenReturn(data);
		deviceService.deleteAll();
		verify(deviceRepositoryMock).findAll();
		verify(deviceRepositoryMock).saveAll(data);
	}

	@Test
	void shouldDeleteAllDevicesIds() {
		Device firstDevice = Device
				.builder()
				.name("Bohemia")
				.company(Company
						.builder()
						.name("Apple")
						.build())
				.build();

		Device secondDevice = Device
				.builder()
				.name("Slovakia")
				.company(Company
						.builder()
						.name("Mac")
						.build())
				.build();

		String[] ids = {firstDevice.getId(), secondDevice.getId()};
		when(deviceRepositoryMock.findById(ids[0])).thenReturn(Optional.of(firstDevice));
		when(deviceRepositoryMock.findById(ids[1])).thenReturn(Optional.of(secondDevice));

		try {
			deviceService.deleteAllById(ids);

			verify(deviceRepositoryMock).save(firstDevice);
			verify(deviceRepositoryMock).save(secondDevice);
		}
		catch (Exception e){
			Assertions.assertThat(false).isTrue();
		}
	}

	@Test
	void shouldFindAllDevices() {
		var data = List.of(
				Device.builder().name("Bohemia").company(Company.builder().name("Apple").build()).build(),
				Device.builder().name("Slovakia").company(Company.builder().name("Mac").build()).build()
		);

		when(deviceRepositoryMock.findAll()).thenReturn(data);
		List<Device> result = deviceService.findAll();
		verify(deviceRepositoryMock).findAll();
		assertEquals(data, result);
	}

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

		var data = List.of(
				Device.builder().name("Bohemia").company(Company.builder().name("Apple").build()).build(),
				Device.builder().name("Slovakia").company(Company.builder().name("Mac").build()).build()
		);

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

		Page<Device> result = deviceService.findAllPageable(pageable);
		verify(deviceRepositoryMock).findAll(pageable);

		assertEquals(pageResult, result);
	}
	// TODO: Do constraint checking for softdelete (non-cascade)
	/*
	// because of FK constraint on SmartMeter
	@Test
	void shouldNotAllowDeleteDeviceById() {
		SmartMeter smartMeter = SmartMeter
									.builder()
									.name("my-smart-device-1")
									.build();
		Device device = Device
				.builder()
				.name("device-with-smart-meter")
				.company(Company
						.builder()
						.name("Apple")
						.build()
				).smartMeterList(
						List.of(smartMeter)
				).build();
		smartMeter.setDevice(device);

		when(deviceRepositoryMock.findById(device.getId())).thenReturn(Optional.of(device));

		assertThrows(DataIntegrityViolationException.class, () -> deviceService.deleteById(device.getId()));
	}
	 */
}
