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

import lombok.Getter;
import org.apache.commons.csv.CSVPrinter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.mte.numecoeval.referentiel.domain.ports.output.ReferentielCsvExportService;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.jpa.entity.EtapeEntity;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

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

class ReferentielCsvExportServiceTest {

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void writeToCsvShouldLogGenericFileError() {
        // given
        var list = new ArrayList<EtapeEntity>();
        list.add(TestDataFactory.EtapeFactory.entity("UTILISATION", "Utilisation"));
        list.add(null);
        var exportService = new TestReferentielCsvExportService(new String[]{"code"}, list);
        var faultyWriter = new Writer() {
            @Override
            public void write(char[] cbuf, int off, int len) throws IOException {
                // Do nothing
            }

            @Override
            public void flush() throws IOException {
                // Do nothing
            }

            @Override
            public void close() throws IOException {
                throw new IOException("Exception de test");
            }
        };

        // When
        assertDoesNotThrow(() -> exportService.writeToCsv(faultyWriter));

        // Then
        assertTrue(exportService.isLogFileErrorCalled());
        assertNotNull(exportService.getLogFileException());
        assertEquals("Exception de test", exportService.getLogFileException().getMessage());
    }

    @Test
    void writeToCsvShouldLogGenericRecordErrorAndProducesCSVWithOnlyHeaders() {
        // given
        var list = new ArrayList<EtapeEntity>();
        list.add(null);
        var exportService = new TestReferentielCsvExportService(new String[]{"code"}, list);
        var stringWriter = new StringWriter();

        // When
        assertDoesNotThrow(() -> exportService.writeToCsv(stringWriter));

        // Then
        assertTrue(exportService.isLogRecordErrorCalled());
        assertNotNull(exportService.getLogRecordException());
        assertEquals(NullPointerException.class, exportService.getLogRecordException().getClass());
        String result = stringWriter.toString();
        assertEquals(
                "code\r\n",
                result
        );
    }

    @Test
    void writeToCsvGivenEmptyListShouldProducesCSVWithOnlyHeaders() {
        // given
        var list = new ArrayList<EtapeEntity>();
        var exportService = new TestReferentielCsvExportService(new String[]{"code"}, list);
        var stringWriter = new StringWriter();

        // When
        assertDoesNotThrow(() -> exportService.writeToCsv(stringWriter));

        // Then
        assertFalse(exportService.isLogRecordErrorCalled());
        assertNull(exportService.getLogRecordException());
        String result = stringWriter.toString();
        assertEquals(
                "code\r\n",
                result
        );
    }

    @Getter
    static
    class TestReferentielCsvExportService extends ReferentielCsvExportService<EtapeEntity> {

        boolean logRecordErrorCalled = false;

        Exception logRecordException = null;

        boolean logFileErrorCalled = false;

        Exception logFileException = null;

        String[] headers;

        List<EtapeEntity> records;

        public TestReferentielCsvExportService(String[] headers, List<EtapeEntity> records) {
            super(EtapeEntity.class);
            this.headers = headers;
            this.records = records;
        }

        @Override
        public String[] getHeaders() {
            return headers;
        }

        @Override
        protected String getObjectId(EtapeEntity object) {
            return object.getCode();
        }

        @Override
        public void printRecord(CSVPrinter csvPrinter, EtapeEntity objectToWrite) throws IOException {
            csvPrinter.printRecord(objectToWrite.getCode());
        }

        @Override
        public List<EtapeEntity> getObjectsToWrite() {
            return records;
        }

        @Override
        public void logRecordError(EtapeEntity objectToWrite, Exception exception) {
            super.logRecordError(objectToWrite, exception);
            logRecordErrorCalled = true;
            logRecordException = exception;
        }

        @Override
        public void logWriterError(Exception exception) {
            super.logWriterError(exception);
            logFileErrorCalled = true;
            logFileException = exception;
        }
    }
}
