package com.workfusion.odf.impl.connector;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Named;

import groovy.lang.Binding;
import org.apache.commons.lang3.StringUtils;
import org.codejargon.feather.Provides;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.springframework.http.MediaType;

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.service.mail.EmailConstants;
import com.workfusion.odf.service.mail.EmailManager;
import com.workfusion.odf.service.mail.entity.EmailAddress;
import com.workfusion.odf.service.mail.entity.EmailAttachment;
import com.workfusion.odf.service.mail.entity.EmailMessage;
import com.workfusion.rpa.core.storage.S3Manager;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class EmailTransactionConnectorTest {
    
    @Inject
    private EmailTransactionConnector emailConnector;

    private S3Manager s3Manager;
    private EmailManager emailManager;
    private Map<String, String> params;
    private Logger logger;

    private Module testModule = new Module() {

        @Provides
        public S3Manager s3Manager() {
            return s3Manager;
        }

        @Provides
        public EmailManager emailManager() {
            return emailManager;
        }

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

        @Provides
        @Named("botConfigParams")
        public Map<String, String> params() {
            return params;
        }

    };

    @BeforeEach
    void init() {
        s3Manager = mock(S3Manager.class);
        params = mock(Map.class);
        emailManager = mock(EmailManager.class);
        logger = mock(Logger.class);
        Binding binding = new Binding();
        Intake.init(binding).override(testModule).injectFields(this).get();
    }

    @Test
    void noAttachment2Mail() {
        // when
        final String docUploadLink = "http://192.168.0.1/doc-upload/file.html";
        when(s3Manager.putFile(any(), any(), any())).thenReturn(docUploadLink);
        when(params.get("input_folder")).thenReturn("");
        when(params.get("mark_as_read")).thenReturn("true");

        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Arrays.asList(message, message));

        // Action
        Collection<Transaction> mailTransactions = emailConnector.get();

        // then
        assertEquals(2, mailTransactions.size());
        // Assert meta data.
        for (Transaction transaction : mailTransactions) {
            assertEquals(6, transaction.getDocs().size());
            List<Document> docs = transaction.getDocs().stream().filter(document -> document.getType().equals(EmailConstants.DOCTYPE_MESSAGE)).collect(Collectors.toList());
            assertEquals(1, docs.size());
            Document doc = docs.get(0);
            assertEquals(message.getFrom().toString(), doc.getAttribute("from"));
            assertEquals(message.getSubject(), doc.getAttribute("subject"));
            assertEquals(emailsToString(message.getTo()), doc.getAttribute("to"));
            assertEquals(EmailConstants.DOCTYPE_MESSAGE, doc.getType());
            assertFalse(doc.getAttribute("cc").isEmpty());
            assertEquals(docUploadLink, doc.getTextLink());

            docs = transaction.getDocs().stream().filter(document -> document.getType().equals(EmailConstants.DOCTYPE_ATTACHMENT)).collect(Collectors.toList());
            assertEquals(2, docs.size());
            for (Document attachmentDoc : docs) {
                assertNotNull(attachmentDoc.getName());
                assertEquals(docUploadLink, attachmentDoc.getOriginalDocumentLink());
            }

            docs = transaction.getDocs().stream().filter(document -> document.getType().equals(EmailConstants.DOCTYPE_INLINE_ATTACHMENT)).collect(Collectors.toList());
            assertEquals(3, docs.size());
            for (Document inlineDoc : docs) {
                assertNotNull(inlineDoc.getName());
                assertEquals(docUploadLink, inlineDoc.getOriginalDocumentLink());
            }
        }
    }

    @Test
    void testCopyToFolder() {
        final String docUploadLink = "http://192.168.0.1/doc-upload/file.html";
        when(s3Manager.putFile(any(), any(), any())).thenReturn(docUploadLink);
        when(params.get("input_folder")).thenReturn("");
        when(params.get("mark_as_read")).thenReturn("true");
        when(params.get(EmailTransactionConnector.EmailSettings.PARAM_POST_ACTION)).thenReturn("copy");
        String targetCopyFolder = "target_copy_folder";
        when(params.get(EmailTransactionConnector.EmailSettings.PARAM_TARGET_FOLDER)).thenReturn(targetCopyFolder);

        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Arrays.asList(message, message));

        // Action
        emailConnector.get();
        verify(emailManager, times(2)).copyMessage(message, targetCopyFolder);
    }

    @Test
    void testMoveToFolder() {
        final String docUploadLink = "http://192.168.0.1/doc-upload/file.html";
        when(s3Manager.putFile(any(), any(), any())).thenReturn(docUploadLink);
        when(params.get("input_folder")).thenReturn("");
        when(params.get("mark_as_read")).thenReturn("true");
        when(params.get(EmailTransactionConnector.EmailSettings.PARAM_POST_ACTION)).thenReturn("move");
        String target_move_folder = "target_move_folder";
        when(params.get(EmailTransactionConnector.EmailSettings.PARAM_TARGET_FOLDER)).thenReturn(target_move_folder);

        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Arrays.asList(message, message));

        // Action
        emailConnector.get();
        verify(emailManager, times(2)).moveMessage(message, target_move_folder);
    }

    @Test
    void testEmptyPostAction() {
        final String docUploadLink = "http://192.168.0.1/doc-upload/file.html";
        when(s3Manager.putFile(any(), any(), any())).thenReturn(docUploadLink);
        when(params.get("input_folder")).thenReturn("");
        when(params.get("mark_as_read")).thenReturn("true");
        when(params.get(EmailTransactionConnector.EmailSettings.PARAM_POST_ACTION)).thenReturn(null);

        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Arrays.asList(message, message));

        // Action
        emailConnector.get();
        verify(emailManager, times(0)).moveMessage(any(), any());
    }

    @Test
    void shouldUseS3BucketToStoreFilesByDefault() {
        // given
        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Collections.singletonList(message));

        // when
        Collection<Transaction> transactions = emailConnector.get();

        // then
        assertEquals(1, transactions.size());
        verify(s3Manager, times(1)).putFile(endsWith(".html"), any(), eq(MediaType.TEXT_HTML_VALUE));
    }

    @Test
    void shouldUseS3BucketWithFolderToStoreFiles() {
        // given
        EmailMessage message = generateMessage();
        when(emailManager.fetchUnreadMessages(anyBoolean())).thenReturn(Collections.singletonList(message));
        when(params.get("mail_s3_folder")).thenReturn("test/folder/for/mails");

        // when
        Collection<Transaction> transactions = emailConnector.get();

        // then
        assertEquals(1, transactions.size());
        verify(s3Manager, times(1)).putFile(matches("test/folder/for/mails/.*.html"), any(), eq(MediaType.TEXT_HTML_VALUE));
    }

    private String emailsToString(List<EmailAddress> emails) {
        return StringUtils.join(emails, ",");
    }

    private EmailMessage generateMessage() {
        return EmailMessage.EmailMessageBuilder.newMessage(
                EmailAddress.of("mturchin@workfusion.com", "Maksim Turchyn"),
                Arrays.asList(
                        EmailAddress.of("from1@workfusion.com", "From 1"),
                        EmailAddress.of("from2@workfusion.com")
                )
        )
                .addCc(EmailAddress.of("cc@workfusion.com"))
                .addBcc(EmailAddress.of("bcc@workfusion.com"))
                .subject("Subject")
                .html("<html></html>")
                .addAttachments(
                        Arrays.asList(
                                new EmailAttachment("attachment1.pdf", new byte[5], MediaType.APPLICATION_PDF_VALUE),
                                new EmailAttachment("attachment2.pnd", new byte[5], MediaType.IMAGE_PNG_VALUE)
                        )
                ).addInlineFiles(
                        Arrays.asList(
                                new EmailAttachment("inline_attachment1.png", new byte[5], MediaType.IMAGE_PNG_VALUE),
                                new EmailAttachment("inline_attachment2.png", new byte[5], MediaType.IMAGE_PNG_VALUE),
                                new EmailAttachment("inline_attachment3.png", new byte[5], MediaType.IMAGE_PNG_VALUE)
                        )
                )
                .build();
    }
}
