package com.atlassian.stash.internal.notification.pull.activity;

import com.atlassian.bitbucket.comment.Comment;
import com.atlassian.bitbucket.comment.CommentAction;
import com.atlassian.bitbucket.notification.NotificationHandler;
import com.atlassian.bitbucket.notification.pull.PullRequestActivityNotification;
import com.atlassian.bitbucket.notification.pull.PullRequestNotification;
import com.atlassian.bitbucket.notification.pull.PullRequestUpdatedNotification;
import com.atlassian.bitbucket.pull.*;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.stash.internal.notification.handlers.NotificationHelper;
import com.atlassian.stash.internal.notification.mention.MentionHelper;
import com.atlassian.stash.internal.notification.usersettings.SendMode;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

public class PullRequestBatchNotificationHandler implements NotificationHandler<PullRequestNotification> {

    private static final Logger log = LoggerFactory.getLogger(PullRequestBatchNotificationHandler.class);

    private final MentionHelper mentionHelper;
    private final NotificationHelper notificationHelper;
    private final PullRequestBatchNotificationDao notificationDao;

    public PullRequestBatchNotificationHandler(MentionHelper mentionHelper,
                                               PullRequestBatchNotificationDao notificationDao,
                                               NotificationHelper notificationHelper) {
        this.mentionHelper = mentionHelper;
        this.notificationDao = notificationDao;
        this.notificationHelper = notificationHelper;
    }

    @Override
    public void handle(PullRequestNotification notification, Iterable<ApplicationUser> recipients) {
        if (notification instanceof PullRequestActivityNotification) {
            handleActivityNotification((PullRequestActivityNotification) notification, recipients);
        } else if (notification instanceof PullRequestUpdatedNotification) {
            handleUpdatedNotification((PullRequestUpdatedNotification) notification, recipients);
        }
    }

    private void handleActivityNotification(PullRequestActivityNotification notification, Iterable<ApplicationUser> recipients) {
        Set<ApplicationUser> batchedUsers = getBatchedUsers(recipients);
        // Note: If there were 0 batched users in recipients, we still have to check for mentions, so don't skip this
        handleActivity(notification, batchedUsers);
    }

    private void handleUpdatedNotification(PullRequestUpdatedNotification notification, Iterable<ApplicationUser> recipients) {
        if (notification.getPreviousToBranch() != null) {
            Set<ApplicationUser> batchedUsers = getBatchedUsers(recipients);
            PullRequest pullRequest = notification.getPullRequest();
            PullRequestBatchId batchId = getBatchId(pullRequest);
            if (!batchedUsers.isEmpty() && batchId != null) {
                String toBranch = pullRequest.getToRef().getDisplayId();
                queueToBranchUpdate(batchedUsers, notification.getTimestamp(), batchId, notification.getUser(), toBranch);
            }
        }
    }

    private void handleActivity(final PullRequestActivityNotification notification, final Set<ApplicationUser> users) {
        final PullRequestActivity activity = notification.getActivity();
        final Long activityId = activity.getId();
        if (activityId == null) {
            log.warn("No PR notification for activity {} because its ID was null", activity);
            return;
        }

        PullRequest pullRequest = activity.getPullRequest();
        final PullRequestBatchId batchId = getBatchId(pullRequest);
        if (batchId == null) {
            // Reason already logged
            return;
        }

        activity.accept(new PullRequestActivityVisitor() {
            @Override
            public void visit(@Nonnull PullRequestActivity activity) {
                PullRequestAction action = activity.getAction();
                if (action == PullRequestAction.OPENED || action == PullRequestAction.UPDATED) {
                    // Opened is handled by an immediate mail, updated is handled separately
                    return;
                }
                if (action == PullRequestAction.REVIEWED) {
                    ApplicationUser author = activity.getPullRequest().getAuthor().getUser();
                    if (users.contains(author)) {
                        queueActivity(Collections.singleton(author), activity.getCreatedDate(), batchId, activityId);
                    }
                } else {
                    queueActivity(users, activity.getCreatedDate(), batchId, activityId);
                }
            }

            @Override
            public void visit(@Nonnull PullRequestCommentActivity activity) {
                handleAndQueueCommentActivity(notification, activity, batchId, users);
            }

            @Override
            public void visit(@Nonnull PullRequestMergeActivity activity) {
                queueActivity(users, activity.getCreatedDate(), batchId, activityId);
            }

            @Override
            public void visit(@Nonnull PullRequestRescopeActivity activity) {
                if (!activity.getFromHash().equals(activity.getPreviousFromHash())) {
                    queueActivity(users, activity.getCreatedDate(), batchId, activityId);
                }
            }


            @Override
            public void visit(@Nonnull PullRequestReviewersUpdatedActivity activity) {
                ApplicationUser author = pullRequest.getAuthor().getUser();
                if (!activity.getUser().equals(author)) {
                    queueActivity(getBatchedUsers(ImmutableSet.of(author)), activity.getCreatedDate(), batchId, activityId);
                }
            }
        });
    }

    @Nullable
    private PullRequestBatchId getBatchId(PullRequest pullRequest) {
        Integer repositoryId = pullRequest.getToRef().getRepository().getId();
        Long pullRequestId = pullRequest.getId();
        if (repositoryId == null) {
            log.warn("No PR notification for pull request {} because its repository ID was null", pullRequest);
            return null;
        }
        if (pullRequestId == null) {
            log.warn("No PR notification for pull request {} because its pull request ID was null", pullRequest);
            return null;
        }

        return PullRequestBatchId.of(repositoryId, pullRequestId);
    }

    private Set<ApplicationUser> getBatchedUsers(Iterable<ApplicationUser> recipients) {
        return notificationHelper.filterBySendMode(recipients, SendMode.batchedSet());
    }

    private void handleAndQueueCommentActivity(PullRequestActivityNotification notification,
                                               PullRequestCommentActivity activity, PullRequestBatchId batchId,
                                               Set<ApplicationUser> users) {
        CommentAction commentAction = activity.getCommentAction();
        Comment comment = activity.getComment();
        if (commentAction == CommentAction.ADDED || commentAction == CommentAction.REPLIED) {
            // STASHDEV-4407: Edit and delete comment actions are ignored

            Iterable<ApplicationUser> mentionedUsers = mentionHelper.findUsers(notification, comment.getText());
            Set<ApplicationUser> mentionedBatchUsers = getBatchedUsers(mentionedUsers);

            CommentData data = new CommentData(comment.getId());
            queueData(Sets.difference(users, mentionedBatchUsers), activity.getCreatedDate(), batchId, data);
            queueData(mentionedBatchUsers, activity.getCreatedDate(), batchId, data);
        }
    }

    private void queueActivity(Iterable<ApplicationUser> users, Date date, PullRequestBatchId batchId, long activityId) {
        queueData(users, date, batchId, new ActivityData(activityId));
    }

    private void queueToBranchUpdate(Iterable<ApplicationUser> users, Date date, PullRequestBatchId batchId, ApplicationUser updater, String toBranch) {
        queueData(users, date, batchId, new ToBranchUpdateData(updater.getId(), toBranch, date));
    }

    private void queueData(Iterable<ApplicationUser> users, Date date, PullRequestBatchId batchId, Data data) {
        notificationDao.queue(users, date, batchId, data);
    }
}
