package at.procon.dip.ingestion.adapter;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import at.procon.dip.domain.access.DocumentAccessContext;
import at.procon.dip.domain.document.DocumentFamily;
import at.procon.dip.domain.document.DocumentStatus;
import at.procon.dip.domain.document.DocumentType;
import at.procon.dip.domain.document.SourceType;
import at.procon.dip.domain.document.entity.Document;
import at.procon.dip.domain.document.service.DocumentService;
import at.procon.dip.ingestion.config.DipIngestionProperties;
import at.procon.dip.ingestion.dto.ImportedDocumentResult;
import at.procon.dip.ingestion.service.GenericDocumentImportService;
import at.procon.dip.ingestion.service.TedPackageChildImportProcessor;
import at.procon.dip.ingestion.service.TedPackageExpansionService;
import at.procon.dip.ingestion.service.TedPackageExpansionService.TedPackageEntry;
import at.procon.dip.ingestion.spi.IngestionResult;
import at.procon.dip.ingestion.spi.SourceDescriptor;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class TedPackageDocumentIngestionAdapterTest {

    @Mock
    private GenericDocumentImportService importService;
    @Mock
    private TedPackageExpansionService expansionService;
    @Mock
    private TedPackageChildImportProcessor childImportProcessor;
    @Mock
    private DocumentService documentService;

    private DipIngestionProperties properties;
    private TedPackageDocumentIngestionAdapter adapter;

    @BeforeEach
    void setUp() {
        properties = new DipIngestionProperties();
        properties.setEnabled(true);
        properties.setTedPackageAdapterEnabled(true);
        properties.setTedPackageChildParallelism(4);
        properties.setTedPackageChildMaxInFlight(4);
        adapter = new TedPackageDocumentIngestionAdapter(properties, importService, expansionService, childImportProcessor, documentService);
    }

    @Test
    void ingest_should_process_ted_children_in_parallel_and_keep_result_order() throws Exception {
        Document rootDocument = Document.builder()
                .id(UUID.randomUUID())
                .documentType(DocumentType.GENERIC_BINARY)
                .documentFamily(DocumentFamily.GENERIC)
                .status(DocumentStatus.RECEIVED)
                .title("package.tar.gz")
                .build();
        when(importService.importDocument(any())).thenReturn(new ImportedDocumentResult(rootDocument, null, List.of(), false));

        List<TedPackageEntry> entries = List.of(
                entry("a.xml"),
                entry("b.xml"),
                entry("c.xml"),
                entry("d.xml")
        );
        doAnswer(invocation -> {
            TedPackageExpansionService.TedPackageEntryConsumer consumer = invocation.getArgument(1);
            for (TedPackageEntry entry : entries) {
                consumer.accept(entry);
            }
            return null;
        }).when(expansionService).streamEntries(any(byte[].class), any(TedPackageExpansionService.TedPackageEntryConsumer.class));

        AtomicInteger active = new AtomicInteger();
        AtomicInteger maxActive = new AtomicInteger();
        when(childImportProcessor.processChild(any(UUID.class), anyString(), any(OffsetDateTime.class), any(), any(TedPackageEntry.class), anyInt()))
                .thenAnswer(invocation -> {
                    int sortOrder = invocation.getArgument(5, Integer.class);
                    int currentActive = active.incrementAndGet();
                    maxActive.accumulateAndGet(currentActive, Math::max);
                    try {
                        Thread.sleep(100);
                    } finally {
                        active.decrementAndGet();
                    }
                    Document childDocument = Document.builder()
                            .id(UUID.nameUUIDFromBytes(("child-" + sortOrder).getBytes(StandardCharsets.UTF_8)))
                            .documentType(DocumentType.TED_NOTICE)
                            .documentFamily(DocumentFamily.PROCUREMENT)
                            .status(DocumentStatus.RECEIVED)
                            .title("child-" + sortOrder)
                            .build();
                    var metadata = documentService.getMetadata(childDocument.getId());
                    return TedPackageChildImportProcessor.ChildImportResult.success(childDocument.getId(), metadata, "warn-" + sortOrder);
                });

        IngestionResult result = adapter.ingest(new SourceDescriptor(
                DocumentAccessContext.publicDocument(),
                SourceType.TED_PACKAGE,
                "PKG-1",
                null,
                "package.tar.gz",
                "application/gzip",
                new byte[]{1, 2, 3},
                null,
                OffsetDateTime.now(),
                null,
                java.util.Map.of()
        ));

        assertThat(maxActive.get()).isGreaterThan(1);
        assertThat(result.documents()).hasSize(1 + entries.size());
        assertThat(result.documents().get(0).title()).isEqualTo("package.tar.gz");
        assertThat(result.documents().subList(1, result.documents().size()))
                .extracting(metadata -> metadata.title())
                .containsExactly("child-1", "child-2", "child-3", "child-4");
        assertThat(result.warnings()).containsExactly("warn-1", "warn-2", "warn-3", "warn-4");

        verify(childImportProcessor, times(entries.size())).processChild(eq(rootDocument.getId()), eq("PKG-1"), any(OffsetDateTime.class), any(), any(TedPackageEntry.class), anyInt());
    }

    private TedPackageEntry entry(String fileName) {
        String path = "folder/" + fileName;
        byte[] xml = ("<notice>" + fileName + "</notice>").getBytes(StandardCharsets.UTF_8);
        return new TedPackageEntry(path, fileName, xml, new String(xml, StandardCharsets.UTF_8));
    }
}
