package org.mte.numecoeval.referentiel.infrastructure.adapter.export;

import org.apache.commons.csv.CSVPrinter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mte.numecoeval.referentiel.domain.model.TypeEquipement;
import org.mte.numecoeval.referentiel.domain.ports.input.impl.ImportTypeEquipementPortImpl;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.jpa.entity.TypeItemEntity;
import org.mte.numecoeval.referentiel.infrastructure.jpa.repository.TypeItemRepository;
import org.mte.numecoeval.referentiel.infrastructure.mapper.TypeEquipementMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.TypeEquipementMapperImpl;
import org.mte.numecoeval.referentiel.infrastructure.mapper.TypeItemMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.TypeItemMapperImpl;
import org.springframework.test.util.ReflectionTestUtils;

import java.io.IOException;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.mte.numecoeval.referentiel.utils.Constants.CATEGORIE_EQUIPEMENT_PHYSIQUE;

class TypeEquipementCsvExportServiceTest {

    @InjectMocks
    TypeEquipementCsvExportService exportService;

    @Mock
    TypeItemRepository repository;
    TypeItemMapper typeItemMapper = new TypeItemMapperImpl();
    TypeEquipementMapper typeEquipementMapper = new TypeEquipementMapperImpl();

    @Mock
    CSVPrinter csvPrinter;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
        ReflectionTestUtils.setField(exportService, "typeItemMapper", typeItemMapper);
        ReflectionTestUtils.setField(exportService, "typeEquipementMapper", typeEquipementMapper);
    }

    private List<TypeItemEntity> typeItemEntities() {
        return List.of(
                TestDataFactory.TypeItemFactory.entity(
                        "Serveur", CATEGORIE_EQUIPEMENT_PHYSIQUE, true, null, 6.0, null, "", "Test", null),
                TestDataFactory.TypeItemFactory.entity(
                        "Communication Device", CATEGORIE_EQUIPEMENT_PHYSIQUE, false, "commentaires", 4.0, null, "SSG",
                        "refItem", null)
        );
    }

    private TypeItemEntity typeItemEntity() {
        return TestDataFactory.TypeItemFactory.entity("Serveur", CATEGORIE_EQUIPEMENT_PHYSIQUE, true, null, 6.0, null, "", "Test", null);
    }

    @Test
    void getHeadersShouldReturnSameHeadersAsImport() {
        Assertions.assertEquals(ImportTypeEquipementPortImpl.getHeaders(), exportService.getHeaders());
    }

    @Test
    void getObjectsToWriteShouldReturnRepositoryFindAll() {
        List<TypeItemEntity> entities = typeItemEntities();
        when(repository.findByCategorie(CATEGORIE_EQUIPEMENT_PHYSIQUE)).thenReturn(entities);
        var actual = exportService.getObjectsToWrite();
        assertEquals(2, actual.size());
        assertEquals("Communication Device,Serveur", actual.stream().map(TypeEquipement::getType).sorted().collect(Collectors.joining(",")));
    }

    @Test
    void printRecordShouldUseEntityAttributes() throws IOException {
        var entity = TestDataFactory.TypeItemFactory.entity(
                "Serveur", null, true, "Exemple de serveur basique",
                6.0, null, "NegaOctet", "serveur_par_defaut", null);
        DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
        df.setMaximumFractionDigits(340);

        assertDoesNotThrow(() -> exportService.printRecord(csvPrinter, typeEquipementMapper.toTypeEquipement(typeItemMapper.toDomain(entity))));

        Mockito.verify(csvPrinter, times(1)).printRecord(
                entity.getType(),
                entity.isServeur(),
                entity.getCommentaire(),
                df.format(entity.getDureeVieDefaut()),
                entity.getSource(),
                entity.getRefItemParDefaut());
    }

    @Test
    void logRecordErrorShouldLogSpecificErrorForRecord() {
        var entity = typeItemEntity();
        assertDoesNotThrow(() -> exportService.logRecordError(typeEquipementMapper.toTypeEquipement(typeItemMapper.toDomain(entity)), new Exception("Test")));
    }

    @Test
    void logRecordErrorShouldLogGenericErrorForRecord() {
        assertDoesNotThrow(() -> exportService.logRecordError(null, new Exception("Test")));
    }

    @Test
    void logRecordErrorShouldLogGenericErrorForFile() {
        assertDoesNotThrow(() -> exportService.logWriterError(new Exception("Test")));
    }

    @Test
    void writeToCsvShouldReturnCSV() {
        // given
        var entities = typeItemEntities();
        when(repository.findByCategorie(CATEGORIE_EQUIPEMENT_PHYSIQUE)).thenReturn(entities);
        StringWriter stringWriter = new StringWriter();

        // when
        exportService.writeToCsv(stringWriter);

        // Then
        String result = stringWriter.toString();
        assertEquals(
                """
                        type;serveur;commentaire;dureeVieDefaut;source;refEquipementParDefaut
                        Serveur;true;;6;;Test
                        Communication Device;false;commentaires;4;SSG;refItem
                        """,
                result.replaceAll("\r\n", "\n")
        );
    }
}
