/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/

package org.apache.james.jmap.draft.methods;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;

import javax.inject.Inject;
import javax.mail.Flags;

import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageIdManager;
import org.apache.james.mailbox.MessageManager.FlagsUpdateMode;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.model.Header;
import org.apache.james.mailbox.model.Headers;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.model.SearchQuery;
import org.apache.james.util.streams.Iterators;
import org.apache.mailet.base.RFC2822Headers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.fge.lambdas.Throwing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ReferenceUpdater {
    public static final String X_FORWARDED_ID_HEADER = "X-Forwarded-Message-Id";
    public static final Flags FORWARDED_FLAG = new Flags("$Forwarded");

    private static final Logger logger = LoggerFactory.getLogger(ReferenceUpdater.class);

    private final MessageIdManager messageIdManager;
    private final MailboxManager mailboxManager;

    @Inject
    public ReferenceUpdater(MessageIdManager messageIdManager, MailboxManager mailboxManager) {
        this.messageIdManager = messageIdManager;
        this.mailboxManager = mailboxManager;
    }

    public Mono<Void> updateReferences(Headers headers, MailboxSession session) throws MailboxException {
        Map<String, String> headersAsMap = Iterators.toStream(headers.headers())
            .collect(ImmutableMap.toImmutableMap(Header::getName, Header::getValue));
        return updateReferences(headersAsMap, session);
    }

    public Mono<Void> updateReferences(Map<String, String> headers, MailboxSession session) throws MailboxException {
        Optional<String> inReplyToId = Optional.ofNullable(headers.get(RFC2822Headers.IN_REPLY_TO));
        Optional<String> forwardedId = Optional.ofNullable(headers.get(X_FORWARDED_ID_HEADER));

        return inReplyToId.map(Throwing.function((String id) -> updateAnswered(id, session)).sneakyThrow()).orElse(Mono.empty())
            .then(forwardedId.map((Throwing.function((String id) -> updateForwarded(id, session)).sneakyThrow())).orElse(Mono.empty()));
    }

    private Mono<Void> updateAnswered(String messageId, MailboxSession session) throws MailboxException {
        return updateFlag(messageId, session, new Flags(Flags.Flag.ANSWERED));
    }

    private Mono<Void> updateForwarded(String messageId, MailboxSession session) throws MailboxException {
        return updateFlag(messageId, session, FORWARDED_FLAG);
    }

    private Mono<Void> updateFlag(String messageId, MailboxSession session, Flags flag) throws MailboxException {
        int limit = 2;
        MultimailboxesSearchQuery searchByRFC822MessageId = MultimailboxesSearchQuery
            .from(SearchQuery.of(SearchQuery.mimeMessageID(messageId)))
            .build();
        return Flux.from(mailboxManager.search(searchByRFC822MessageId, session, limit))
            .collectList()
            .flatMap(references -> {
                MessageId reference = Iterables.getOnlyElement(references);
                return Flux.from(messageIdManager.messageMetadata(reference, session))
                    .map(metaData -> metaData.getComposedMessageId().getMailboxId())
                    .collect(ImmutableList.toImmutableList())
                    .flatMap(mailboxIds -> Mono.from(messageIdManager.setFlagsReactive(flag, FlagsUpdateMode.ADD, reference, mailboxIds, session)));
            })
            .onErrorResume(NoSuchElementException.class, e -> {
                logger.info("Unable to find a message with this Mime Message Id: " + messageId);
                return Mono.empty();
            })
            .onErrorResume(IllegalArgumentException.class, e -> {
                logger.info("Too many messages are matching this Mime Message Id: " + messageId);
                return Mono.empty();
            });
    }
}
