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

import org.apache.commons.csv.CSVPrinter;
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.ImpactReseau;
import org.mte.numecoeval.referentiel.domain.ports.input.impl.ImportImpactReseauPortImpl;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.jpa.entity.FacteurCaracterisationEntity;
import org.mte.numecoeval.referentiel.infrastructure.jpa.repository.FacteurCaracterisationRepository;
import org.mte.numecoeval.referentiel.infrastructure.mapper.FacteurCaracterisationMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.FacteurCaracterisationMapperImpl;
import org.mte.numecoeval.referentiel.infrastructure.mapper.ImpactReseauMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.ImpactReseauMapperImpl;
import org.mte.numecoeval.referentiel.utils.Constants;
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;

class ImpactReseauCsvExportServiceTest {

    @InjectMocks
    ImpactReseauCsvExportService exportService;

    @Mock
    FacteurCaracterisationRepository repository;
    FacteurCaracterisationMapper facteurCaracterisationMapper = new FacteurCaracterisationMapperImpl();
    ImpactReseauMapper reseauMapper = new ImpactReseauMapperImpl();
    @Mock
    CSVPrinter csvPrinter;

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

    private List<FacteurCaracterisationEntity> facteurCaracterisationEntities() {
        return List.of(
                TestDataFactory.FacteurCaracterisationFactory.entity("Fixed-line network",
                        TestDataFactory.EtapeFactory.entity("UTILISATION", ""),
                        TestDataFactory.CritereFactory.entity("Changement climatique", "", ""),
                        "", Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS, "Ecran", 0.1, "", 0.2, "", "NegaOctet"
                ),
                TestDataFactory.FacteurCaracterisationFactory.entity("Mobile network",
                        TestDataFactory.EtapeFactory.entity("DISTRIBUTION", ""),
                        TestDataFactory.CritereFactory.entity("Changement climatique", "", ""),
                        "", Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS, "Ecran", 0.01, "", 0.002, "", ""
                )
        );
    }


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

    @Test
    void getObjectsToWriteShouldReturnRepositoryFindAll() {
        var entities = facteurCaracterisationEntities();
        when(repository.findByNiveauAndTiers(Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS)).thenReturn(entities);

        var actual = exportService.getObjectsToWrite();
        assertEquals(2, actual.size());
        assertEquals("DISTRIBUTION,UTILISATION", actual.stream().map(ImpactReseau::getEtape).sorted().collect(Collectors.joining(",")));
    }

    @Test
    void printRecordShouldUseEntityAttributes() throws IOException {
        var entity = TestDataFactory.FacteurCaracterisationFactory.entity("Fixed-line network",
                TestDataFactory.EtapeFactory.entity("UTILISATION", ""),
                TestDataFactory.CritereFactory.entity("Changement climatique", "", ""),
                "", Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS, "Ecran", 0.1, "", 0.2, "", "NegaOctet"
        );

        DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
        df.setMaximumFractionDigits(340);

        assertDoesNotThrow(() -> exportService.printRecord(csvPrinter, reseauMapper.toImpactReseau(facteurCaracterisationMapper.toDomain(entity))));

        Mockito.verify(csvPrinter, times(1)).printRecord(entity.getNom(),
                entity.getEtape(), entity.getCritere(),
                df.format(entity.getValeur()), df.format(entity.getConsoElecMoyenne()),
                entity.getSource());
    }

    @Test
    void logRecordErrorShouldLogSpecificErrorForRecord() {
        var entity = TestDataFactory.FacteurCaracterisationFactory.entity("Fixed-line network",
                TestDataFactory.EtapeFactory.entity("UTILISATION", ""),
                TestDataFactory.CritereFactory.entity("Changement climatique", "", ""),
                "", Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS, "Ecran", 0.1, "", 0.2, "", "NegaOctet"
        );

        assertDoesNotThrow(() -> exportService.logRecordError(reseauMapper.toImpactReseau(facteurCaracterisationMapper.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 = facteurCaracterisationEntities();
        when(repository.findByNiveauAndTiers(Constants.RESEAU_NIVEAU, Constants.RESEAU_TIERS)).thenReturn(entities);
        StringWriter stringWriter = new StringWriter();

        // when
        exportService.writeToCsv(stringWriter);

        // Then
        String result = stringWriter.toString();
        assertEquals(
                "refReseau;etapeACV;critere;valeur;consoElecMoyenne;source\r\n" +
                        "Fixed-line network;UTILISATION;Changement climatique;0.2;0.1;NegaOctet\r\n" +
                        "Mobile network;DISTRIBUTION;Changement climatique;0.002;0.01;\r\n",
                result
        );
    }


}
