package com.workfusion.odf.impl.processor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
import javax.inject.Inject;

import groovy.lang.Binding;
import org.apache.commons.io.IOUtils;
import org.codejargon.feather.Provides;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;

import com.workfusion.odf.api.domain.Document;
import com.workfusion.odf.api.domain.Transaction;
import com.workfusion.odf.core.Intake;
import com.workfusion.odf.core.Module;
import com.workfusion.odf.impl.converter.extractor.zip.RecursiveZipExtractor;
import com.workfusion.odf.impl.processor.extractor.ZipFlatMapProcessor;
import com.workfusion.rpa.core.storage.S3Manager;

import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
public class ZipFlatMapProcessorTest {
    private static final String ZIP_EXTRACTOR_EXAMPLE_FOR_FLAT_MAP_ZIP = "/zipExtractor/exampleForFlatMap.zip";
    private static final String ZIP_EXTRACTOR_EMPTY_ZIP = "/zipExtractor/empty.zip";

    @Inject
    private ZipFlatMapProcessor zipFlatMapProcessor;

    private S3Manager s3Manager;

    private RecursiveZipExtractor recursiveZipExtractor;

    private Logger logger;

    private Module testModule = new Module(){
        @Provides
        public S3Manager s3Manager() {
            return s3Manager;
        }

        @Provides
        public Logger logger() {
            return logger;
        }

        @Provides
        public RecursiveZipExtractor recursiveZipExctractor() {
            return recursiveZipExtractor;
        }
    };

    @BeforeEach
    public void init() {
        s3Manager = mock(S3Manager.class);
        logger = mock(Logger.class);
        recursiveZipExtractor = mock( RecursiveZipExtractor.class);
        Binding binding = new Binding();

        Intake intake = Intake.init(binding).override(testModule).injectFields(this).get();
    }


    @Test
    public void convertEmptyZipTestSuccess() throws IOException {
        //given

        final Transaction transaction = getTransactionWithEmptyZipAndNotZipDocument();

        final byte[] zip = IOUtils.resourceToByteArray(ZIP_EXTRACTOR_EMPTY_ZIP);
        Mockito.when(s3Manager.getContent(ZIP_EXTRACTOR_EMPTY_ZIP)).thenReturn(zip);

        //when
        final Transaction result = zipFlatMapProcessor.transform(transaction);

        //then
        Assertions.assertEquals(1, result.getDocs().stream().filter(Document::isZipDocument).count());//check that zip file was filtered
        Assertions.assertEquals( 0, result.getDocs().stream().filter(this::isValidLinkAfterProcessingZip).count()); //check that there are no extracted from zip documents
        Assertions.assertEquals(2, result.getDocs().size());//
    }

    @Test
    public void convertBigZipTestSuccess() throws IOException {
        //given

        final Transaction transaction = getMixedTransaction();
        final byte[] zip = IOUtils.resourceToByteArray(ZIP_EXTRACTOR_EXAMPLE_FOR_FLAT_MAP_ZIP);
        Mockito.when(s3Manager.getContent(ZIP_EXTRACTOR_EXAMPLE_FOR_FLAT_MAP_ZIP)).thenReturn(zip);
        Mockito.when(s3Manager.uploadFileToS3(Mockito.any(), Mockito.any(), Mockito.any())).thenAnswer(i -> i.getArgument(1)  );

        //when
        final Transaction result = zipFlatMapProcessor.transform(transaction);

        //then
        Assertions.assertTrue(result.getDocs().stream().filter(Document::isZipDocument).allMatch(e-> e.getId().equals(e.getOrigin().getId()) && e.getId()!=null));//check that all ids are set, unique and not inherited from parent doc
        Assertions.assertTrue(result.getDocs().stream().filter(Document::isZipDocument).allMatch(e-> e.getName().equals(e.getOrigin().getName()) && e.getName()!=null));//check that all names are set, unique and not inherited from parent doc
        Assertions.assertEquals(0, result.getDocs().stream().filter(Document::isZipDocument).count());// check that zip files disappeared
        Assertions.assertEquals( 20, result.getDocs().stream().filter(this::isValidLinkAfterProcessingZip).count());// check that amount of extracted files equals to to an actual number of inner files
        Assertions.assertEquals(21, result.getDocs().size());// amount of documents is amount of inner zip files + not zip documents from transaction
    }


    @Test
    public void convertDocumentTestSuccessBlankTransaction() {
        //given

        final Transaction transaction =  new Transaction();
        transaction.setDocs(new ArrayList<>());
        transaction.setId("1");

        Assertions.assertEquals(0, transaction.getDocs().stream().filter(e -> isZIPFile(e.getOriginalDocumentLink())).count());
        Assertions.assertEquals(0, transaction.getDocs().size());

        //when
        final Transaction result = zipFlatMapProcessor.transform(transaction);

        //then
        Assertions.assertEquals(0, result.getDocs().stream().filter(e -> isZIPFile(e.getOriginalDocumentLink())).count());
        Assertions.assertEquals(0, result.getDocs().size());
    }


    private Transaction getMixedTransaction() {
        final Transaction transaction = new Transaction();
        transaction.setId(UUID.randomUUID().toString());

        Document zipDoc = createZipDocument();

        Document notZipDoc = createNotZipDocument();

        addDocumentsToTransaction(transaction, zipDoc, notZipDoc);
        return transaction;
    }

    private Transaction getTransactionWithEmptyZipAndNotZipDocument() {
        final Transaction transaction = new Transaction();
        transaction.setId(UUID.randomUUID().toString());

        Document zipDoc = createEmptyZipDocument();

        Document notZipDoc = createNotZipDocument();

        addDocumentsToTransaction(transaction, zipDoc, notZipDoc);
        return transaction;
    }

    private void addDocumentsToTransaction(Transaction transaction, Document ...documents) {
        if(transaction.getDocs()==null){
            transaction.setDocs(new ArrayList<>());
        }

        for (Document document : documents) {
            transaction.getDocs().add(document);
        }
    }

    private Document createNotZipDocument() {
        Document notZipDoc = new Document();
        notZipDoc.setOriginalDocumentLink("not zip ulr");
        notZipDoc.setId("notZipId");
        notZipDoc.setName("nameOfNotZip");
        return notZipDoc;
    }

    private Document createZipDocument() {
        Document zipDoc = new Document();
        zipDoc.setId("zipID");
        zipDoc.setName("exampleForFlatMap.zip");
        zipDoc.setOriginalDocumentLink(ZIP_EXTRACTOR_EXAMPLE_FOR_FLAT_MAP_ZIP);
        return zipDoc;
    }

    private Document createEmptyZipDocument() {
        Document zipDoc = new Document();
        zipDoc.setId("emptyZipID");
        zipDoc.setName("empty.zip");
        zipDoc.setOriginalDocumentLink(ZIP_EXTRACTOR_EMPTY_ZIP);
        return zipDoc;
    }
    private boolean isValidLinkAfterProcessingZip(Document e) {
        final String[] splitedName = e.getOriginalDocumentLink().split("\\.(?!.*?\\|.*?)");
        final String fileExtension = splitedName.length==2 ? "\\."+splitedName[1] : "";
        return e.getOriginalDocumentLink().matches(".*zip_flatMap/\\b[0-9a-f]{8}\\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\\b[0-9a-f]{12}\\b" + (fileExtension.equals(e.getName()) ? "" : fileExtension) );
    }

    private  boolean isZIPFile(String str) {
        return str !=null && str.endsWith(".zip");
    }
}