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

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.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.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.house.HouseCreateDto;
import cz.muni.fi.pa165.model.dto.house.HouseDto;
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.Arrays;
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")
public class SmartMeterControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SmartMeterFacade smartMeterFacade;

    @Autowired
    private DeviceFacade deviceFacade;
    @Autowired
    private HouseFacade houseFacade;

    @Autowired
    private SmartMeterService smartMeterService;

    @Autowired
    private HouseService houseService;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private CompanyService companyService;

    private DeviceDto deviceDto;
    private HouseDto houseDto;

    private final static String URL = "/api/smart-meter";
    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());
        this.deviceDto = deviceFacade.create(deviceCreateDto);

        HouseCreateDto house = new HouseCreateDto();
        house.setCity("Brno");
        house.setAddress("Chrvatska 2");
        this.houseDto = houseFacade.create(house);
    }
    @BeforeEach
    void setUp() {

        SmartMeterCreateDto sm1 = new SmartMeterCreateDto();
        sm1.setName("My superdevice1");
        sm1.setHouseId(houseDto.getId());
        sm1.setDeviceId(deviceDto.getId());

        SmartMeterCreateDto sm2 = new SmartMeterCreateDto();
        sm2.setName("My superdevice2");
        sm2.setHouseId(houseDto.getId());
        sm2.setDeviceId(deviceDto.getId());

        SmartMeterCreateDto sm3 = new SmartMeterCreateDto();
        sm3.setName("My superdevice3");
        sm3.setHouseId(houseDto.getId());
        sm3.setDeviceId(deviceDto.getId());

        Arrays.asList(sm1, sm2, sm3).forEach(smartMeterFacade::create);
    }

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

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

    @Test
    @Disabled // JWT token
    void deleteSmartMeter() throws Exception {
        List<SmartMeterDto> smartMeterDtoList = smartMeterFacade.findAll();
        assertTrue(smartMeterDtoList.size() > 0);
        SmartMeterDto smartMeterToDelete = smartMeterDtoList.get(0);
        assertDoesNotThrow(() -> smartMeterService.findById(smartMeterToDelete.getId()));

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

        // Verify
        SmartMeterDto smartMeterDto = objectMapper.readValue(response, SmartMeterDto.class);
        assertThat(smartMeterDto.getId()).isEqualTo(smartMeterToDelete.getId());
        assertThat(smartMeterDto.getName()).isEqualTo(smartMeterToDelete.getName());
        assertThrows(EntityNotFoundException.class, () -> smartMeterService.findById(smartMeterToDelete.getId()));
    }


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

        assertThat(smartMeterDtoResult.getPage()).isEqualTo(0);
        assertThat(smartMeterDtoResult.getPageSize()).isEqualTo(10);
        assertThat(smartMeterDtoResult.getTotal()).isEqualTo(3);

        final SmartMeterDto firstSmartMeterFromResponse = smartMeterDtoResult.getItems().get(0);
        SmartMeterDto smartMeterDto1 = new SmartMeterDto();
        smartMeterDto1.setName("My superdevice1");
        assertThat(firstSmartMeterFromResponse.getName()).isEqualTo(smartMeterDto1.getName());

        final SmartMeterDto secondSmartMeterFromResponse = smartMeterDtoResult.getItems().get(1);
        SmartMeterDto smartMeterDto2 = new SmartMeterDto();
        smartMeterDto2.setName("My superdevice2");
        assertThat(secondSmartMeterFromResponse.getName()).isEqualTo(smartMeterDto2.getName());

        final SmartMeterDto thirdSmartMeterFromResponse = smartMeterDtoResult.getItems().get(2);
        SmartMeterDto smartMeterDto3 = new SmartMeterDto();
        smartMeterDto3.setName("My superdevice3");
        assertThat(thirdSmartMeterFromResponse.getName()).isEqualTo(smartMeterDto3.getName());
    }
}
