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

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import cz.muni.fi.pa165.core.company.Company;
import cz.muni.fi.pa165.core.company.CompanyService;
import cz.muni.fi.pa165.core.device.DeviceController;
import cz.muni.fi.pa165.core.device.DeviceFacade;
import cz.muni.fi.pa165.core.device.DeviceService;
import cz.muni.fi.pa165.core.house.HouseFacade;
import cz.muni.fi.pa165.core.house.HouseService;
import cz.muni.fi.pa165.core.smartmeter.SmartMeterFacade;
import cz.muni.fi.pa165.core.smartmeter.SmartMeterService;
import cz.muni.fi.pa165.model.dto.common.Result;
import cz.muni.fi.pa165.model.dto.device.DeviceCreateDto;
import cz.muni.fi.pa165.model.dto.device.DeviceDto;
import cz.muni.fi.pa165.model.dto.device.DeviceUpdateDto;
import cz.muni.fi.pa165.model.dto.house.HouseCreateDto;
import cz.muni.fi.pa165.model.dto.metrics.MetricsCreateDto;
import cz.muni.fi.pa165.model.dto.metrics.MetricsDto;
import cz.muni.fi.pa165.model.dto.smartDevice.SmartMeterCreateDto;
import cz.muni.fi.pa165.model.dto.smartDevice.SmartMeterDto;
import jakarta.persistence.EntityNotFoundException;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@WithMockUser
@AutoConfigureMockMvc(addFilters = false)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestPropertySource(locations = "classpath:application-test.properties")
class MetricsControllerTest {
	@Autowired
	private MockMvc mockMvc;
	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private DeviceFacade deviceFacade;

	@Autowired
	private MetricsFacade metricsFacade;

	@Autowired
	private SmartMeterFacade smartMeterFacade;

	@Autowired
	private HouseFacade houseFacade;

	@Autowired
	private HouseService houseService;

	@Autowired
	private DeviceService deviceService;

	@Autowired
	private SmartMeterService smartMeterService;

	@Autowired
	private MetricsService metricsService;

	@Autowired
	private CompanyService companyService;

	private SmartMeterDto smartMeterDto;

	private final static String URL = "/api/metric";
	private final static String CONTENT_TYPE = "application/json";



	@BeforeAll
	void prepare() {
		Company company = Company.builder().name("Apple").build();
		companyService.create(company);

		DeviceCreateDto deviceCreateDto = new DeviceCreateDto();
		deviceCreateDto.setName("Something");
		deviceCreateDto.setCompanyId(company.getId());

		HouseCreateDto house = new HouseCreateDto();
		house.setCity("Brno");
		house.setAddress("Chrvatska 2");

		SmartMeterCreateDto sm1 = new SmartMeterCreateDto();
		sm1.setName("My superdevice1");
		sm1.setHouseId(houseFacade.create(house).getId());
		sm1.setDeviceId(deviceFacade.create(deviceCreateDto).getId());

		this.smartMeterDto = smartMeterFacade.create(sm1);
	}

	@BeforeEach
	void setUp() {
		MetricsCreateDto metricsCreateDto1 = new MetricsCreateDto();
		metricsCreateDto1.setConsumptionKWH(234.2);
		metricsCreateDto1.setSmartMeterId(smartMeterDto.getId());
		metricsFacade.create(metricsCreateDto1);

		MetricsCreateDto metricsCreateDto2 = new MetricsCreateDto();
		metricsCreateDto2.setConsumptionKWH(111.3);
		metricsCreateDto2.setSmartMeterId(smartMeterDto.getId());
		metricsFacade.create(metricsCreateDto2);
	}

	@AfterEach
	void dropAll() {
		metricsService.hardDeleteAll();
	}

	@AfterAll
	void dropAllPrepared() {
		smartMeterService.hardDeleteAll();
		houseService.hardDeleteAll();
		deviceService.hardDeleteAll();
		companyService.hardDeleteAll();
	}

	@Test
	void findAllMetricsTest() throws Exception{
		// Execute
		String response = mockMvc.perform(get(URL + "?page=0")
						.contentType(CONTENT_TYPE))
				.andExpect(status().isOk())
				.andReturn()
				.getResponse()
				.getContentAsString();
		Result<MetricsDto> metricsDtoResult = objectMapper.readValue(response, new TypeReference<>() {
		});

		assertThat(metricsDtoResult.getPage()).isEqualTo(0);
		assertThat(metricsDtoResult.getPageSize()).isEqualTo(10);
		assertThat(metricsDtoResult.getTotal()).isEqualTo(2);

		final MetricsDto firstMetricsFromResponse = metricsDtoResult.getItems().get(0);
		MetricsDto metricsDto1 = new MetricsDto();
		metricsDto1.setConsumptionKWH(234.2);
		assertThat(firstMetricsFromResponse.getConsumptionKWH()).isEqualTo(metricsDto1.getConsumptionKWH());

		final MetricsDto secondMetricsFromResponse = metricsDtoResult.getItems().get(1);
		MetricsDto metricsDto2 = new MetricsDto();
		metricsDto2.setConsumptionKWH(111.3);
		assertThat(secondMetricsFromResponse.getConsumptionKWH()).isEqualTo(metricsDto2.getConsumptionKWH());
	}

	@Test
	void createMetricsTest() throws Exception {
		// Prepare
		MetricsCreateDto metricsCreateDto = new MetricsCreateDto();
		metricsCreateDto.setConsumptionKWH(2323.12);
		metricsCreateDto.setSmartMeterId(smartMeterDto.getId());

		// Execute
		String response = mockMvc.perform(post(URL)
						.contentType(CONTENT_TYPE)
						.content(objectMapper.writeValueAsString(metricsCreateDto)))
				.andExpect(status().isCreated())
				.andReturn()
				.getResponse()
				.getContentAsString();

		// Verify
		MetricsDto metricsDto = objectMapper.readValue(response, MetricsDto.class);
		assertThat(metricsDto.getId()).isNotNull();
		assertThat(metricsDto.getConsumptionKWH()).isEqualTo(metricsCreateDto.getConsumptionKWH());
	}

	@Test
	void deleteMetricByIdTest() throws Exception {
		List<MetricsDto> metricsDtoList = metricsFacade.findAll();
		assertTrue(metricsDtoList.size() > 0);
		MetricsDto metricsToDelete = metricsDtoList.get(0);
		assertDoesNotThrow(() -> metricsService.findById(metricsToDelete.getId()));

		// Execute
		String response = mockMvc.perform(delete(
						URL + "/{id}", metricsToDelete.getId())
						.contentType(CONTENT_TYPE))
				.andExpect(status().isOk())
				.andReturn()
				.getResponse()
				.getContentAsString();

		// Verify
		MetricsDto metricsDto = objectMapper.readValue(response, MetricsDto.class);
		assertThat(metricsDto.getId()).isEqualTo(metricsToDelete.getId());
		assertThat(metricsDto.getConsumptionKWH()).isEqualTo(metricsToDelete.getConsumptionKWH());
		assertThrows(EntityNotFoundException.class, () -> metricsService.findById(metricsToDelete.getId()));
	}
}