package com.workfusion.odf.mail.impl.javax;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import com.workfusion.odf.service.mail.entity.EmailAddress;
import com.workfusion.odf.service.mail.entity.EmailMessage;
import microsoft.exchange.webservices.data.core.PropertySet;
import microsoft.exchange.webservices.data.core.service.schema.ItemSchema;
import microsoft.exchange.webservices.data.search.filter.SearchFilter;
import org.awaitility.Awaitility;
import org.awaitility.core.ConditionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class EwsEmailsClientTest {

    private static final String INBOX_FOLDER = "Inbox";
    private static final Duration MAX_EMAIL_TIMEOUT = Duration.ofMinutes(1);

    private final ConditionFactory await = Awaitility.await().pollInterval(1, TimeUnit.SECONDS).atMost(MAX_EMAIL_TIMEOUT);

    private final AtomicInteger counter = new AtomicInteger();

    private EwsEmailsClient client;

    private String emailId;

    private final String server = "https://outlook.office365.com/EWS/Exchange.asmx";
    private final String username = "rpa-coe-gf@workfusion.com";
    private final String password = "uX878eHV3AQtJBE";

    @BeforeEach
    void setUp() {
        client = new EwsEmailsClient(server, username, password);
        emailId = UUID.randomUUID().toString();
    }

    @AfterEach
    void tearDown() {
        testScopedMessages().forEach(client::removeMessage);

        await.until(() -> testScopedMessages().isEmpty());
    }

    @Test
    void shouldSendAndReceiveMessage() throws InterruptedException {
        // given
        sendEmail();

        // then
        await.untilAsserted(() -> assertThat(testScopedUnreadCount()).as("Inbox should contain 1 unread message").isEqualTo(1));
    }

    @Test
    void shouldMarkEmailAsRead() throws InterruptedException {
        //given
        sendEmail();
        sendEmail();
        sendEmail();

        await.until(() -> testScopedUnreadCount() == 3);

        //when
        testScoped(client.fetchAllMessages(INBOX_FOLDER)).findFirst().ifPresent(client::markEmailAsRead);

        //then
        await.untilAsserted(() -> assertThat(testScopedUnreadCount()).as("Unread message count should change to 2").isEqualTo(2));
    }

    @Test
    void shouldSendAndReceiveMessageWithHighPriority() throws InterruptedException {
        //given
        final EmailMessage emailMessage = EmailMessage.EmailMessageBuilder
                .newMessage(EmailAddress.of(username), EmailAddress.of(username))
                .subject(emailId + " " + counter.getAndIncrement())
                .text("Hello World")
                .importance("High")
                .build();

        client.sendEmail(emailMessage);

        await.until(() -> testScopedUnreadCount() == 1);

        //then
        await.untilAsserted(() -> {
            final Optional<EmailMessage> message = testScoped(client.fetchAllMessages(INBOX_FOLDER)).findFirst();

            assertThat(message).isPresent();
            assertThat(message.get().getImportance()).as("Message should have high priority").isEqualTo("High");
        });
    }

    private void sendEmail() {
        EmailMessage emailMessage = EmailMessage.EmailMessageBuilder.newMessage(EmailAddress.of(username), EmailAddress.of(username))
                .subject(emailId + " " + counter.getAndIncrement())
                .text("Hello World").build();

        client.sendEmail(emailMessage);
    }

    private Stream<EmailMessage> testScoped(Collection<EmailMessage> messages) {
        return messages.stream()
                .filter(msg -> msg.getSubject().startsWith(emailId));
    }

    private long testScopedUnreadCount() {
        return testScoped(client.fetchUnreadMessages(INBOX_FOLDER, false)).count();
    }

    private List<EmailMessage> testScopedMessages() {
        return client.fetchMessages(INBOX_FOLDER,
                PropertySet.FirstClassProperties,
                new SearchFilter.ContainsSubstring(ItemSchema.Subject, emailId),
                false);
    }
}
