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

import cz.muni.fi.pa165.core.company.Company;
import cz.muni.fi.pa165.core.device.Device;
import cz.muni.fi.pa165.core.house.House;
import cz.muni.fi.pa165.core.smartmeter.SmartMeter;
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 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;

class MetricsServiceTest {


	@InjectMocks
	private MetricsService metricsService;

	@Mock
	private MetricsRepository metricsRepository;

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

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		when(metricsRepository.save(metrics)).thenReturn(metrics);

		Metrics createdMetrics = metricsService.create(metrics);

		verify(metricsRepository).save(metrics);

		assertThat(createdMetrics).isEqualTo(metrics);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics metrics2 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Page<Metrics> page = new PageImpl<>(List.of(metrics1, metrics2));

		int pageNumber = 0;
		int pageSize = 10;

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

		Page<Metrics> result = metricsService.findAllPageable(PageRequest.of(pageNumber, pageSize));

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

		assertThat(result).containsExactlyInAnyOrder(metrics1, metrics2);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics metrics2 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		var data = List.of(metrics1, metrics2);

		when(metricsRepository.findAll()).thenReturn(data);
		List<Metrics> result = metricsService.findAll();
		verify(metricsRepository).findAll();
		assertEquals(data, result);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics metrics2 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Page<Metrics> page = new PageImpl<>(List.of(metrics1, metrics2));

		int pageNumber = 0;
		int pageSize = 10;

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

		Page<Metrics> result = metricsService.findAllPageableInt(pageNumber);

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

		assertThat(result).containsExactlyInAnyOrder(metrics1, metrics2);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		when(metricsRepository.findById(metrics.getId())).thenReturn(Optional.of(metrics));

		Metrics result = metricsService.findById(metrics.getId());

		verify(metricsRepository).findById(metrics.getId());

		assertEquals(metrics, result);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics metrics2 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		var data = List.of(metrics1, metrics2);

		when(metricsRepository.findAll()).thenReturn(data);
		metricsService.deleteAll();
		verify(metricsRepository).findAll();
		verify(metricsRepository).saveAll(data);
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics metrics2 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		String[] ids = {metrics1.getId(), metrics2.getId()};
		when(metricsRepository.findById(ids[0])).thenReturn(Optional.of(metrics1));
		when(metricsRepository.findById(ids[1])).thenReturn(Optional.of(metrics2));

		try{
			metricsService.deleteAllById(ids);
			verify(metricsRepository).save(metrics1);
			verify(metricsRepository).save(metrics2);
		}catch(Exception e){
			Assertions.assertThat(false).isTrue();
		}
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics metrics1 = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		when(metricsRepository.findById(metrics1.getId())).thenReturn(Optional.of(metrics1));

		try {
			Metrics deletedMetrics = metricsService.deleteById(metrics1.getId());
			verify(metricsRepository).findById(metrics1.getId());
			verify(metricsRepository).save(metrics1); // soft delete

			assertNotNull(deletedMetrics.getDeletedAt());
			assertEquals(metrics1, deletedMetrics);

		} catch (Exception e){
			Assertions.assertThat(false).isTrue();
		}
	}

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

		House house = House
				.builder()
				.state("CR")
				.address("lsakdj")
				.city("sdfa")
				.build();

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

		SmartMeter smartMeter = SmartMeter
				.builder()
				.name("meter-1")
				.device(device)
				.house(house)
				.build();

		Metrics originalMetrics = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(100.0)
				.build();

		Metrics updatedMetrics = Metrics
				.builder()
				.smartMeter(smartMeter)
				.consumptionKWH(300.0)
				.build();

		when(metricsRepository.findById(originalMetrics.getId())).thenReturn(Optional.of(originalMetrics));

		when(metricsRepository.save(updatedMetrics)).thenReturn(updatedMetrics);

		Metrics result = metricsService.update(updatedMetrics, originalMetrics.getId());

		verify(metricsRepository).findById(originalMetrics.getId());
		verify(metricsRepository).save(updatedMetrics);

		assertEquals(originalMetrics.getId(), result.getId());
		assertEquals(updatedMetrics.getConsumptionKWH(), result.getConsumptionKWH());
		assertEquals(updatedMetrics.getSmartMeter().getName(), result.getSmartMeter().getName());
	}

	@Test
	void shouldHardDeleteAll() {
		metricsService.hardDeleteAll();
		verify(metricsRepository).deleteAll();
	}


}