package at.procon.dip.ingestion.adapter;

import at.procon.dip.classification.spi.DetectionResult;
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.RelationType;
import at.procon.dip.domain.document.SourceType;
import at.procon.dip.domain.document.entity.Document;
import at.procon.dip.domain.document.service.DocumentRelationService;
import at.procon.dip.domain.document.repository.DocumentSourceRepository;
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.MailMessageExtractionService;
import at.procon.dip.ingestion.service.MailMetadataPersistenceService;
import at.procon.dip.ingestion.mail.MailImportIdentityResolver;
import at.procon.dip.ingestion.spi.IngestionResult;
import at.procon.dip.ingestion.spi.SourceDescriptor;
import at.procon.ted.service.attachment.ZipExtractionService;
import at.procon.dip.testsupport.MailBundleTestSupport;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

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

@ExtendWith(MockitoExtension.class)
class MailDocumentIngestionAdapterBundleTest {

    private static Path bundleRoot;

    @Mock
    private GenericDocumentImportService importService;
    @Mock
    private DocumentRelationService relationService;
    @Mock
    private ZipExtractionService zipExtractionService;

    @Mock
    private MailMetadataPersistenceService mailMetadataPersistenceService;

    @Mock
    private DocumentSourceRepository documentSourceRepository;

    private MailDocumentIngestionAdapter adapter;

    @BeforeAll
    static void extractBundle() throws Exception {
        bundleRoot = MailBundleTestSupport.extractBundleToTempDir();
    }

    @BeforeEach
    void setUp() {
        var properties = new DipIngestionProperties();
        properties.setEnabled(true);
        properties.setMailAdapterEnabled(true);
        properties.setExpandMailZipAttachments(false);
        properties.setMailImportBatchId("test-mail-bundle");
        lenient().when(zipExtractionService.canHandle(any(), any())).thenReturn(false);
        lenient().when(documentSourceRepository.findBySourceTypeAndExternalSourceId(any(), any())).thenReturn(java.util.Optional.empty());
        adapter = new MailDocumentIngestionAdapter(properties, importService, new MailMessageExtractionService(), relationService, zipExtractionService, new MailImportIdentityResolver(), mailMetadataPersistenceService, documentSourceRepository);
    }

    @ParameterizedTest(name = "ingest {0}")
    @MethodSource("at.procon.dip.testsupport.MailBundleTestSupport#bundleMailNames")
    void ingest_should_import_root_and_all_attachments_from_bundle(String fileName) throws Exception {
        Path eml = bundleRoot.resolve("eml").resolve(fileName);
        byte[] rawMime = Files.readAllBytes(eml);
        List<String> expectedAttachmentNames = MailBundleTestSupport.EXPECTED_ATTACHMENT_NAMES.get(fileName);

        AtomicInteger sequence = new AtomicInteger();
        when(importService.importDocument(any(SourceDescriptor.class))).thenAnswer(invocation -> {
            SourceDescriptor sd = invocation.getArgument(0);
            int idx = sequence.incrementAndGet();
            Document document = Document.builder()
                    .id(UUID.nameUUIDFromBytes((fileName + ":" + idx).getBytes()))
                    .documentType(sd.sourceType() == SourceType.MAIL && "message/rfc822".equals(sd.mediaType()) ? DocumentType.MIME_MESSAGE : DocumentType.GENERIC_BINARY)
                    .documentFamily(DocumentFamily.MAIL)
                    .status(DocumentStatus.RECEIVED)
                    .title(sd.fileName())
                    .mimeType(sd.mediaType())
                    .dedupHash(Integer.toHexString(idx))
                    .build();
            DetectionResult detection = new DetectionResult(document.getDocumentType(), document.getDocumentFamily(), document.getMimeType(), null, java.util.Map.of());
            return new ImportedDocumentResult(document, detection, List.of(), false);
        });

        SourceDescriptor source = new SourceDescriptor(
                DocumentAccessContext.publicDocument(),
                SourceType.MAIL,
                fileName,
                eml.toUri().toString(),
                fileName,
                "message/rfc822",
                rawMime,
                null,
                OffsetDateTime.now(),
                null,
                java.util.Map.of()
        );

        IngestionResult result = adapter.ingest(source);

        assertEquals(1 + expectedAttachmentNames.size(), result.documents().size(), "root + each attachment should be imported");
        assertTrue(result.warnings().isEmpty(), "bundle sample should import without warnings: " + fileName);

        ArgumentCaptor<SourceDescriptor> sourceCaptor = ArgumentCaptor.forClass(SourceDescriptor.class);
        verify(importService, times(1 + expectedAttachmentNames.size())).importDocument(sourceCaptor.capture());
        verify(relationService, times(expectedAttachmentNames.size())).ensureRelation(any());

        List<SourceDescriptor> descriptors = sourceCaptor.getAllValues();
        SourceDescriptor root = descriptors.getFirst();
        assertEquals("message/rfc822", root.mediaType());
        assertNotNull(root.textContent(), "root mail should carry serialized message text");
        assertEquals(fileName, root.fileName());

        List<String> importedAttachmentNames = new ArrayList<>();
        for (int i = 1; i < descriptors.size(); i++) {
            importedAttachmentNames.add(descriptors.get(i).fileName());
        }
        assertEquals(expectedAttachmentNames, importedAttachmentNames);
    }

    @ParameterizedTest(name = "octet-stream preview guard {0}")
    @MethodSource("octetStreamMailNames")
    void ingest_should_not_pass_preview_text_for_generic_octet_stream_attachments(String fileName) throws Exception {
        Path eml = bundleRoot.resolve("eml").resolve(fileName);
        byte[] rawMime = Files.readAllBytes(eml);

        when(importService.importDocument(any(SourceDescriptor.class))).thenAnswer(invocation -> {
            SourceDescriptor sd = invocation.getArgument(0);
            Document document = Document.builder()
                    .id(UUID.randomUUID())
                    .documentType(DocumentType.UNKNOWN)
                    .documentFamily(DocumentFamily.MAIL)
                    .status(DocumentStatus.RECEIVED)
                    .title(sd.fileName())
                    .mimeType(sd.mediaType())
                    .build();
            DetectionResult detection = new DetectionResult(DocumentType.UNKNOWN, DocumentFamily.MAIL, sd.mediaType(), null, java.util.Map.of());
            return new ImportedDocumentResult(document, detection, List.of(), false);
        });

        adapter.ingest(new SourceDescriptor(
                DocumentAccessContext.publicDocument(),
                SourceType.MAIL,
                fileName,
                eml.toUri().toString(),
                fileName,
                "message/rfc822",
                rawMime,
                null,
                OffsetDateTime.now(),
                null,
                java.util.Map.of()
        ));

        ArgumentCaptor<SourceDescriptor> sourceCaptor = ArgumentCaptor.forClass(SourceDescriptor.class);
        verify(importService, atLeast(1)).importDocument(sourceCaptor.capture());
        List<SourceDescriptor> attachments = sourceCaptor.getAllValues().subList(1, sourceCaptor.getAllValues().size());
        assertEquals(3, attachments.size());
        assertTrue(attachments.stream().allMatch(sd -> sd.textContent() == null),
                "octet-stream attachments should not get inline preview text");
    }

    static java.util.stream.Stream<String> octetStreamMailNames() {
        return java.util.stream.Stream.of("sample-mail-05-generic-octet-stream.eml");
    }
}
