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

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.workfusion.odf.mail.impl.javax.exception.EwsException;
import com.workfusion.odf.service.mail.MessageConverter;
import com.workfusion.odf.service.mail.entity.EmailAddress;
import com.workfusion.odf.service.mail.entity.EmailAttachment;
import com.workfusion.odf.service.mail.entity.EmailFlag;
import com.workfusion.odf.service.mail.entity.EmailMessage;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.PropertySet;
import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet;
import microsoft.exchange.webservices.data.core.enumeration.property.BodyType;
import microsoft.exchange.webservices.data.core.enumeration.property.Importance;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
import microsoft.exchange.webservices.data.core.service.folder.Folder;
import microsoft.exchange.webservices.data.core.service.item.Item;
import microsoft.exchange.webservices.data.core.service.schema.EmailMessageSchema;
import microsoft.exchange.webservices.data.property.complex.Attachment;
import microsoft.exchange.webservices.data.property.complex.EmailAddressCollection;
import microsoft.exchange.webservices.data.property.complex.FileAttachment;
import microsoft.exchange.webservices.data.property.complex.FolderId;
import microsoft.exchange.webservices.data.property.complex.MessageBody;
import org.apache.commons.lang.StringUtils;

public class EwsMessageConverter extends MessageConverter<microsoft.exchange.webservices.data.core.service.item.EmailMessage> {

    private ExchangeService service;

    EwsMessageConverter(ExchangeService service) {
        super();
        this.service = service;
    }

    @Override
    public microsoft.exchange.webservices.data.core.service.item.EmailMessage createNativeMessage(EmailMessage emailMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage msg;
        // native email message
        try {
            msg = new microsoft.exchange.webservices.data.core.service.item.EmailMessage(service);
        } catch (Exception e) {
            throw new EwsException("Could not create new message", e);
        }
        //body
        MessageBody messageBody = new MessageBody();
        if (emailMessage.getHtml().isPresent()) {
            messageBody.setBodyType(BodyType.HTML);
            messageBody.setText(emailMessage.getHtml().get());

        } else {
            messageBody.setText(emailMessage.getText());
        }

        try {
            msg.setBody(messageBody);
        } catch (Exception e) {
            throw new EwsException("Could not set body", e);
        }
        //subject
        try {
            msg.setSubject(emailMessage.getSubject());
        } catch (Exception e) {
            throw new EwsException("Could not set subject", e);
        }
        //address from
        microsoft.exchange.webservices.data.property.complex.EmailAddress addressFrom = new microsoft.exchange.webservices.data.property.complex.EmailAddress();
        addressFrom.setAddress(emailMessage.getFrom() != null ? emailMessage.getFrom().getAddress().replaceAll(" ", "") : null);
        try {
            msg.setFrom(addressFrom);
        } catch (Exception e) {
            throw new EwsException("Could not set from addresses", e);
        }
        //address to
        emailMessage.getTo().forEach(m -> {
            try {
                msg.getToRecipients().add(m.getAddress());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not set to addresses", e);
            }
        });
        //attachments
        emailMessage.getAttachments().forEach(att ->
        {
            try {
                FileAttachment fileAttachment = msg.getAttachments().addFileAttachment(att.getFileName(), att.getContent());
                fileAttachment.setIsInline(false);
                fileAttachment.setContentType(att.getMimeType());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not set attachments", e);
            }
        });
        // bcc
        emailMessage.getBcc().forEach(m -> {
            try {
                msg.getBccRecipients().add(m.getAddress());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not set bcc addresses", e);
            }
        });
        // cc
        emailMessage.getCc().forEach(m -> {
            try {
                msg.getCcRecipients().add(m.getAddress());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not set cc addresses", e);
            }
        });
        // flag  read
        try {
            msg.setIsRead(emailMessage.getFlags().stream().anyMatch(f -> f.equals(EmailFlag.READ)));
        } catch (Exception e) {
            throw new EwsException("Could not set is read property", e);
        }

        // inline attachments
        emailMessage.getInlineFiles().forEach(att -> {
            try {
                FileAttachment fileAttachment = msg.getAttachments().addFileAttachment(att.getFileName(), att.getContent());
                fileAttachment.setIsInline(true);
                fileAttachment.setContentId(att.getFileName());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not set inline attachments", e);
            }
        });

        if (StringUtils.isNotBlank(emailMessage.getImportance())) {
            try {
                msg.setImportance(Importance.valueOf(emailMessage.getImportance()));
            } catch (Exception e) {
                throw new EwsException("Could not set importance", e);
            }
        }

        emailMessage.getInternetMessageId().ifPresent(id -> {
            try {
                msg.getPropertyBag().setObjectFromPropertyDefinition(EmailMessageSchema.InternetMessageId, id);
            } catch (Exception e) {
                throw new EwsException("Could not set internetMessageId", e);
            }
        });

        return msg;
    }

    @Override
    public EmailMessage convertToEmailMessage(microsoft.exchange.webservices.data.core.service.item.EmailMessage nativeMessage) {
        EmailAddress from = extractFrom(nativeMessage);
        List<EmailAddress> to = extractTo(nativeMessage);

        EmailMessage.EmailMessageBuilder builder = EmailMessage.EmailMessageBuilder.newMessage(from, to);
        builder.id(extractId(nativeMessage))
                .internetMessageId(extractInternetMessageId(nativeMessage))
                .addCc(extractCc(nativeMessage))
                .addBcc(extractBcc(nativeMessage))
                .html(extractHtml(nativeMessage))
                .text(extractText(nativeMessage))
                .subject(extractSubject(nativeMessage))
                .addAttachments(extractAttachments(nativeMessage))
                .addInlineFiles(extractInlineAttachments(nativeMessage))
                .importance(extractImportance(nativeMessage));

        return builder.build();
    }

    protected String extractImportance(microsoft.exchange.webservices.data.core.service.item.EmailMessage nativeMessage) {
        try {
            return nativeMessage.getImportance().toString();
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve importance", e);
        }
    }

    protected String extractId(Item nativeMessage) {
        try {
            return nativeMessage.getId().toString();
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve id", e);
        }
    }

    protected String extractInternetMessageId(Item nativeMessage) {
        try {
            return nativeMessage instanceof microsoft.exchange.webservices.data.core.service.item.EmailMessage
                    ? ((microsoft.exchange.webservices.data.core.service.item.EmailMessage) nativeMessage).getInternetMessageId()
                    : null;
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve internetMessageId", e);
        }
    }

    protected EmailAddress extractFrom(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            microsoft.exchange.webservices.data.property.complex.EmailAddress emailAddress;
            try {
                emailAddress = emailMessage.getFrom();
                return EmailAddress.of(emailAddress.getAddress());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve from address", e);
            }
        }
        return null;
    }

    protected List<EmailAddress> extractReplyTo(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            try {
                return extractEmailAddressCollection(emailMessage.getReplyTo());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve reply to", e);
            }
        }
        return Collections.emptyList();
    }

    protected List<EmailAddress> extractTo(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            try {
                return extractEmailAddressCollection(emailMessage.getToRecipients());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve to address", e);
            }
        }
        return Collections.emptyList();
    }

    protected List<EmailAddress> extractCc(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            try {
                return extractEmailAddressCollection(emailMessage.getCcRecipients());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve cc address", e);
            }
        }
        return Collections.emptyList();
    }

    protected List<EmailAddress> extractBcc(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            try {
                return extractEmailAddressCollection(emailMessage.getBccRecipients());
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve bcc address", e);
            }
        }
        return Collections.emptyList();
    }

    protected ZonedDateTime extractDate(Item nativeMessage) {

        try {
            return ZonedDateTime.ofInstant(nativeMessage.getDateTimeReceived().toInstant(),
                    ZoneId.systemDefault());
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve received date", e);
        }
    }

    protected String extractHtml(Item nativeMessage) {
        try {
            if (nativeMessage.getBody().getBodyType() == BodyType.HTML) {
                return nativeMessage.getBody().toString();
            } else {
                return "";
            }
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve html", e);
        }
    }

    protected String extractText(Item nativeMessage) {
        try {
            PropertySet textPropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
            textPropertySet.setRequestedBodyType(BodyType.Text);
            microsoft.exchange.webservices.data.core.service.item.EmailMessage textMsg =
                    microsoft.exchange.webservices.data.core.service.item.EmailMessage
                            .bind(service, nativeMessage.getId(), textPropertySet);
            MessageBody body = textMsg.getBody();
            return body.toString();
        } catch (Exception e) {
            throw new EwsException("Could not retrieve text", e);
        }
    }

    protected String extractSubject(Item nativeMessage) {
        try {
            return nativeMessage.getSubject();
        } catch (ServiceLocalException e) {
            throw new EwsException("Could not retrieve subject", e);
        }
    }

    protected List<EmailAttachment> extractAttachments(Item nativeMessage) {
        List<EmailAttachment> emailAttachments = new ArrayList<>();
        try {
            if (nativeMessage.getHasAttachments()) {

                for (Attachment a : nativeMessage.getAttachments()) {
                    if (!a.getIsInline()) {
                        String name = a.getName();
                        byte[] content = new byte[0];
                        String contentType = null;
                        if (a instanceof FileAttachment) {
                            FileAttachment fa = (FileAttachment) a;
                            fa.load();
                            content = fa.getContent();
                            contentType = fa.getContentType();
                        }
                        EmailAttachment emailAttachment = new EmailAttachment(name, content, contentType);
                        emailAttachments.add(emailAttachment);
                    }

                }
            }
        } catch (Exception e) {
            throw new EwsException("Could not retrieve attachments", e);
        }
        return emailAttachments;
    }

    protected List<EmailAttachment> extractInlineAttachments(Item nativeMessage) {
        List<EmailAttachment> emailAttachments = new ArrayList<>();
        try {
            for (Attachment a : nativeMessage.getAttachments()) {
                if (a.getIsInline()) {
                    String name = a.getName();
                    byte[] content = new byte[0];
                    String contentType = null;
                    if (a instanceof FileAttachment) {
                        FileAttachment fa = (FileAttachment) a;
                        fa.load();
                        content = fa.getContent();
                        contentType = fa.getContentType();
                    }
                    EmailAttachment emailAttachment = new EmailAttachment(name, content, contentType);
                    emailAttachments.add(emailAttachment);
                }
            }
        } catch (Exception e) {
            throw new EwsException("Could not retrieve inline attachments", e);
        }
        return emailAttachments;
    }

    protected String extractParentFolder(Item nativeMessage) {
        try {
            FolderId folderId = nativeMessage.getParentFolderId();
            Folder folder = Folder.bind(this.service, folderId);
            return folder.getDisplayName();
        } catch (Exception e) {
            throw new EwsException("Could not retrieve parent folder", e);
        }
    }

    protected Set<EmailFlag> extractFlags(Item nativeMessage) {
        microsoft.exchange.webservices.data.core.service.item.EmailMessage emailMessage;
        emailMessage = itemConverter(nativeMessage);
        if (emailMessage != null) {
            Set<EmailFlag> emailFlags = new HashSet<>();
            try {
                emailFlags.add(emailMessage.getIsRead() ? EmailFlag.READ : EmailFlag.UNREAD);
            } catch (ServiceLocalException e) {
                throw new EwsException("Could not retrieve flags", e);
            }
            return emailFlags;
        }
        return Collections.emptySet();
    }

    private microsoft.exchange.webservices.data.core.service.item.EmailMessage itemConverter(Item item) {
        if (item instanceof microsoft.exchange.webservices.data.core.service.item.EmailMessage) {
            return (microsoft.exchange.webservices.data.core.service.item.EmailMessage) item;
        }
        return null;
    }

    private List<EmailAddress> extractEmailAddressCollection(EmailAddressCollection addressCollection) {
        return addressCollection.getItems().stream()
                .map(it -> EmailAddress.of(it.getAddress(), it.getName()))
                .collect(Collectors.toList());
    }
}
