package org.mte.numecoeval.referentiel.infrastructure.restapi.controller.impactreseau;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mte.numecoeval.referentiel.infrastructure.restapi.dto.ImpactReseauDTO;
import org.mte.numecoeval.referentiel.domain.exception.ReferentielException;
import org.mte.numecoeval.referentiel.infrastructure.adapter.export.ImpactReseauCsvExportService;
import org.mte.numecoeval.referentiel.infrastructure.restapi.dto.id.ImpactReseauIdDTO;
import org.mte.numecoeval.referentiel.infrastructure.restapi.facade.ImpactReseauFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ContextConfiguration(classes = {ReferentielImpactReseauRestApiImpl.class})
@ExtendWith(SpringExtension.class)
class ReferentielImpactReseauRestApiImplTest {
    @MockBean
    private ImpactReseauFacade referentielFacade;

    @MockBean
    private ImpactReseauCsvExportService csvExportService;

    @Autowired
    private ReferentielImpactReseauRestApiImpl referentielRestApi;

    @Test
    void get_shouldCallFacadeGetAndReturnMatchingDTO() throws ReferentielException {
        String refReseau = "ImpactReseauMobileMoyen";
        String nomCritere = "Changement Climatique";
        String codeEtapeACV = "UTILISATION";
        var idDTO = ImpactReseauIdDTO.builder()
                .refReseau(refReseau)
                .etapeACV(codeEtapeACV)
                .critere(nomCritere)
                .build();
        var expectedDTO = ImpactReseauDTO.builder()
                .etapeACV(codeEtapeACV)
                .critere(nomCritere)
                .refReseau(refReseau)
                .source("Test")
                .valeur(1.0).build();

        when(referentielFacade.get(idDTO)).thenReturn(expectedDTO);

        var receivedDTO = referentielRestApi.get(refReseau, nomCritere, codeEtapeACV);
        assertSame(receivedDTO, receivedDTO);
        verify(referentielFacade).get(idDTO);
    }

    @Test
    void get_whenNotFound_thenShouldThrowReferentielException() throws ReferentielException {
        String refReseau = "ImpactReseauMobileMoyen";
        String nomCritere = "Changement Climatique";
        String codeEtapeACV = "UTILISATION";
        var idDTO = ImpactReseauIdDTO.builder()
                .refReseau(refReseau)
                .etapeACV(codeEtapeACV)
                .critere(nomCritere)
                .build();

        when(referentielFacade.get(idDTO)).thenThrow(new ReferentielException("Impact Réseau non trouvé"));
        var exception = assertThrows(ReferentielException.class,() -> referentielRestApi.get(refReseau, nomCritere, codeEtapeACV));
        assertEquals("Impact Réseau non trouvé", exception.getMessage());
    }

    @Test
    void add_shouldCallFacadeAddOrUpdate() throws ReferentielException {
        ImpactReseauDTO impactReseauDTO = new ImpactReseauDTO("ImpactReseauMobileMoyen", "UTILISATION",
                "Changement Climatique", "kg CO² eq", "La source est obligatoire", 10.0d, 10.0d);
        when(referentielFacade.addOrUpdate(any())).thenReturn(impactReseauDTO);

        ResponseEntity<ImpactReseauDTO> actualAddResult = referentielRestApi.add(impactReseauDTO);

        assertTrue(actualAddResult.hasBody());
        assertTrue(actualAddResult.getHeaders().isEmpty());
        assertEquals(HttpStatus.OK, actualAddResult.getStatusCode());
        assertEquals(impactReseauDTO, actualAddResult.getBody());
        verify(referentielFacade).addOrUpdate(any());
    }

    @Test
    void add_whenEmptySource_shouldThrowBadRequestException() {
        ImpactReseauDTO impactReseauDTO = new ImpactReseauDTO("ImpactReseauMobileMoyen", "UTILISATION",
                "Changement Climatique", "kg CO² eq", null, 10.0d, 10.0d);

        var exception = assertThrows(ResponseStatusException.class, () -> referentielRestApi.add(impactReseauDTO));
        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
        assertEquals("La source est obligatoire", exception.getReason());
    }

    @Test
    void add_whenNullValue_shouldThrowBadRequestException() {
        var exception = assertThrows(ResponseStatusException.class, () -> referentielRestApi.add(null));
        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
        assertEquals("Le corps de la requête ne peut être null", exception.getReason());
    }

    @Test
    void update_shouldCallFacadeAddOrUpdate() throws ReferentielException {
        when(referentielFacade.addOrUpdate(any())).thenReturn(new ImpactReseauDTO());
        ImpactReseauDTO impactReseauDTO = new ImpactReseauDTO("ImpactReseauMobileMoyen", "UTILISATION",
                "Changement Climatique", "kg CO² eq", "La source est obligatoire", 10.0d, 10.0d);

        ResponseEntity<ImpactReseauDTO> actualAddResult = referentielRestApi.update(impactReseauDTO);

        assertTrue(actualAddResult.hasBody());
        assertTrue(actualAddResult.getHeaders().isEmpty());
        assertEquals(HttpStatus.OK, actualAddResult.getStatusCode());
        verify(referentielFacade).addOrUpdate(any());
    }

    @Test
    void update_whenEmptySource_shouldThrowBadRequestException() throws ReferentielException {
        when(referentielFacade.addOrUpdate(any())).thenReturn(new ImpactReseauDTO());
        ImpactReseauDTO impactReseauDTO = new ImpactReseauDTO("ImpactReseauMobileMoyen", "UTILISATION",
                "Changement Climatique", "kg CO² eq", "", 10.0d, 10.0d);

        var exception = assertThrows(ResponseStatusException.class, () -> referentielRestApi.update(impactReseauDTO));
        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
        assertEquals("La source est obligatoire", exception.getReason());
    }

    @Test
    void update_whenNullValue_shouldThrowBadRequestException() {
        var exception = assertThrows(ResponseStatusException.class, () -> referentielRestApi.update(null));
        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
        assertEquals("Le corps de la requête ne peut être null", exception.getReason());
    }

    @Test
    void importCSV_shouldCallPurgeAndAddAll() throws IOException, ReferentielException {
        doNothing().when(referentielFacade).purgeAndAddAll(any());
        referentielRestApi.importCSV(new MockMultipartFile("Name", "AAAAAAAA".getBytes(StandardCharsets.UTF_8)));
        verify(referentielFacade).purgeAndAddAll(any());
    }

    @Test
    void importCSV_whenEmptyFileThenShouldThrowException() throws ReferentielException {
        doNothing().when(referentielFacade).purgeAndAddAll(any());
        MockMultipartFile file = new MockMultipartFile("Name", (byte[]) null);
        ResponseStatusException responseStatusException = assertThrows(ResponseStatusException.class, () -> referentielRestApi.importCSV(file));
        assertEquals(HttpStatus.BAD_REQUEST, responseStatusException.getStatusCode());
        assertEquals("Le fichier n'existe pas ou alors il est vide", responseStatusException.getReason());
    }

    @Test
    void importCSV_whenNullFileThenShouldThrowException() throws ReferentielException {
        doNothing().when(referentielFacade).purgeAndAddAll(any());
        ResponseStatusException responseStatusException = assertThrows(ResponseStatusException.class, () -> referentielRestApi.importCSV(null));
        assertEquals(HttpStatus.BAD_REQUEST, responseStatusException.getStatusCode());
        assertEquals("Le fichier n'existe pas ou alors il est vide", responseStatusException.getReason());
    }

    @Test
    void exportCSV_shouldReturnCSVFile() {
        var servletResponse = new MockHttpServletResponse();

        assertDoesNotThrow(() -> referentielRestApi.exportCSV(servletResponse));

        assertEquals("text/csv;charset=UTF-8", servletResponse.getContentType());
        String headerContentDisposition = servletResponse.getHeader("Content-Disposition");
        assertNotNull(headerContentDisposition);
        assertTrue(headerContentDisposition.contains("attachment; filename=impactReseaux-"));
        assertEquals(200, servletResponse.getStatus());

    }
}

