package com.atlassian.stash.internal.notification.pullrequest;

import com.atlassian.bitbucket.event.pull.*;
import com.atlassian.bitbucket.notification.pull.*;
import com.atlassian.bitbucket.pull.PullRequestParticipant;
import com.atlassian.bitbucket.pull.PullRequestParticipantStatus;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.event.api.EventListener;
import com.atlassian.stash.internal.notification.NotificationManager;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.util.List;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableList;

/**
 * Convert PullRequest Events -> Notifications
 */
@Component
public class PullRequestNotificationProducer {

    private final NotificationManager notificationManager;

    @Autowired
    public PullRequestNotificationProducer(@Nonnull NotificationManager notificationManager) {
        this.notificationManager = notificationManager;
    }

    @EventListener
    public void onActivityEvent(PullRequestActivityEvent event) {
        PullRequestActivityNotification notification = new PullRequestActivityNotification(event.getActivity(), event.getDate(), event.getUser());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestCommentAdded(PullRequestCommentAddedEvent event) {
        // The notifications are sent from CommitDiscussionNotificationProducer.onCommitDiscussionCommentAdded
        if (!(event instanceof PullRequestCommitCommentAddedEvent)) {
            CommentAddedPullRequestNotification notification = new CommentAddedPullRequestNotification(event.getPullRequest(), event.getDate(), event.getUser(), event.getComment(), event.getAnchor());
            notificationManager.dispatch(notification);
        }
    }

    @EventListener
    public void onPullRequestCommentReplied(PullRequestCommentRepliedEvent event) {
        CommentRepliedPullRequestNotification notification = new CommentRepliedPullRequestNotification(event.getPullRequest(), event.getDate(), event.getUser(), event.getComment(), event.getParent());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestUpdatedEvent(PullRequestUpdatedEvent event) {
        notificationManager.dispatch(new PullRequestUpdatedNotification(event.getPullRequest(), event.getDate(),
                event.getUser(), event.getPreviousTitle(), event.getPreviousDescription(), event.getPreviousToBranch()));
    }

    @EventListener
    public void onPullRequestOpened(PullRequestOpenedEvent event) {
        dispatchReviewerAddedNotification(event, event.getPullRequest().getReviewers().stream()
                .map(PullRequestParticipant::getUser)
                .collect(toImmutableList()));
        notificationManager.dispatch(new PullRequestOpenedNotification(event.getPullRequest(), event.getDate(), event.getUser()));
    }

    @EventListener
    public void onPullRequestReviewersUpdated(PullRequestReviewersUpdatedEvent event) {
        dispatchReviewerAddedNotification(event, Lists.newArrayList(event.getAddedReviewers()));
        dispatchReviewerUpdatedNotification(event);
    }

    @EventListener
    public void onPullRequestMerged(PullRequestMergedEvent event) {
        PullRequestMergedNotification notification = new PullRequestMergedNotification(event.getPullRequest(),
                event.getDate(), event.getUser(), event.getCommit());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestClosed(PullRequestDeclinedEvent event) {
        PullRequestClosedNotification notification = new PullRequestClosedNotification(event.getPullRequest(), event.getDate(), event.getUser());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestReopened(PullRequestReopenedEvent event) {
        PullRequestReopenedNotification notification = new PullRequestReopenedNotification(event.getPullRequest(), event.getDate(), event.getUser());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestRescoped(PullRequestRescopedEvent event) {
        // Only fire notification if src ref has changed
        if (event.isFromHashUpdated()) {
            PullRequestFromRefRescopedNotification notification = new PullRequestFromRefRescopedNotification(
                    event.getPullRequest(), event.getDate(), event.getUser(), event.getPreviousFromHash(),
                    event.getPreviousToHash());

            notificationManager.dispatch(notification);
        }
    }

    @EventListener
    public void onPullRequestReviewed(PullRequestParticipantReviewedEvent event) {
        PullRequestNotification notification = event.getParticipant().isApproved() ?
            new PullRequestApprovedNotification(event.getPullRequest(), event.getDate(), event.getUser()) :
            new PullRequestReviewedNotification(event.getPullRequest(), event.getDate(), event.getUser());
        notificationManager.dispatch(notification);
    }

    @EventListener
    public void onPullRequestUnapproved(PullRequestParticipantUnapprovedEvent event) {
        if (event.getPreviousStatus() != PullRequestParticipantStatus.NEEDS_WORK) {
            PullRequestUnapprovedNotification notification = new PullRequestUnapprovedNotification(event.getPullRequest(),
                    event.getDate(), event.getUser());
            notificationManager.dispatch(notification);
        }
    }

    private void dispatchReviewerAddedNotification(PullRequestEvent event, List<ApplicationUser> reviewers) {
        if (!reviewers.isEmpty()) {
            PullRequestReviewerAddedNotification notification =
                    new PullRequestReviewerAddedNotification(event.getPullRequest(), event.getDate(), event.getUser(),
                            reviewers);
            notificationManager.dispatch(notification);
        }
    }

    private void dispatchReviewerUpdatedNotification(PullRequestRolesUpdatedEvent event) {
        notificationManager.dispatch(
                new PullRequestReviewersUpdatedNotification(event.getPullRequest(), event.getDate(), event.getUser(),
                        event.getAddedReviewers(), event.getRemovedReviewers()));
    }
}
