package com.atlassian.stash.internal.audit;

import com.atlassian.bitbucket.event.pull.*;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.bitbucket.audit.AuditEntry;
import com.atlassian.bitbucket.audit.Priority;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestParticipant;
import com.atlassian.bitbucket.request.RequestManager;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

@Component
public class PullRequestEventListener extends AbstractAuditEventListener {

    private static final Function<PullRequestParticipant, String> TO_PARTICIPANT_NAME = new Function<PullRequestParticipant, String>() {

        @Override
        public String apply(PullRequestParticipant participant) {
            return participant.getUser().getName();
        }
    };

    @Autowired
    public PullRequestEventListener(AuditEntryLoggingService auditLoggingService,
                                    RequestManager requestManager,
                                    AuthenticationContext authContext,
                                    EventPublisher eventPublisher) {
        super(auditLoggingService, requestManager, authContext, eventPublisher);
    }

    @EventListener
    public void onPullRequestApproved(PullRequestApprovalEvent event) throws Exception {
        auditPullRequestEvent(event, Collections.<String, Object>singletonMap("participant", event.getParticipant().getUser().getName()));
    }

    @EventListener
    public void onPullRequestDeclined(PullRequestDeclinedEvent event) throws Exception {
        auditPullRequestEvent(event, null);
    }

    @EventListener
    public void onPullRequestMerged(PullRequestMergedEvent event) throws Exception {
        auditPullRequestEvent(event, Collections.<String, Object>singletonMap("changes", toMaps(event.getRefChanges())));
    }

    @EventListener
    public void onPullRequestOpened(PullRequestOpenedEvent event) throws Exception {
        auditPullRequestEvent(event, toOpeningExtrasMap(event.getPullRequest(), null));
    }

    @EventListener
    public void onPullRequestOpenRequested(PullRequestOpenRequestedEvent event) throws Exception {
        auditPullRequestEvent(event, toOpeningExtrasMap(event.getPullRequest(), event.getReviewers()));
    }

    @EventListener
    public void onPullRequestReopened(PullRequestReopenedEvent event) throws Exception {
        auditPullRequestEvent(event, toOpeningExtrasMap(event.getPullRequest(), null));
    }

    @EventListener
    public void onPullRequestParticipantsUpdated(PullRequestParticipantsUpdatedEvent event) throws Exception {
        ImmutableMap.Builder<String, Object> builder = getMapBuilder();
        addPullRequestParticipantChangedDetails(builder, event.getAddedParticipants(), event.getRemovedParticipants());
        auditPullRequestEvent(event, builder.build());
    }

    @EventListener
    public void onPullRequestReviewersUpdated(PullRequestReviewersUpdatedEvent event) throws Exception {
        ImmutableMap.Builder<String, Object> builder = getMapBuilder();
        addPullRequestParticipantChangedDetails(builder, event.getAddedReviewers(), event.getRemovedReviewers());
        auditPullRequestEvent(event, builder.build());
    }

    private void addPullRequestParticipantChangedDetails(ImmutableMap.Builder<String, Object> builder,
                                                         Set<ApplicationUser> added, Set<ApplicationUser> removed) {
        if (!added.isEmpty()) {
            builder.put("added", Collections2.transform(added, ApplicationUser::getName));
        }
        if (!removed.isEmpty()) {
            builder.put("removed", Collections2.transform(removed, ApplicationUser::getName));
        }
    }

    private void auditPullRequestEvent(PullRequestEvent event, Map<String, Object> extras) throws Exception {
        PullRequest pullRequest = event.getPullRequest();
        ImmutableMap.Builder<String, Object> builder = getMapBuilder();
        safePut(builder, "id", pullRequest.getId());
        if (extras != null) {
            builder.putAll(extras);
        }
        AuditEntry auditEntry = createAuditEntryBuilder(event)
                .details(convertToJsonString(builder.build()))
                .target(toProjectAndRepositoryString(pullRequest.getToRef().getRepository()))
                .build();
        publish(event, auditEntry, Priority.LOW);
    }

    private ImmutableMap<String, Object> toOpeningExtrasMap(PullRequest pullRequest, Set<ApplicationUser> reviewers) {
        ImmutableMap.Builder<String, Object> mapBuilder = getMapBuilder();
        if (CollectionUtils.isNotEmpty(reviewers)) {
            mapBuilder.put("reviewers", Collections2.transform(reviewers, ApplicationUser::getName));
        } else if (CollectionUtils.isNotEmpty(pullRequest.getReviewers())) {
            mapBuilder.put("reviewers", Collections2.transform(pullRequest.getReviewers(), TO_PARTICIPANT_NAME));
        }
        if (CollectionUtils.isNotEmpty(pullRequest.getParticipants())) {
            mapBuilder.put("participants", Collections2.transform(pullRequest.getParticipants(), TO_PARTICIPANT_NAME));
        }
        return mapBuilder
                .put("source", toProjectAndRepositoryMap(pullRequest.getFromRef().getRepository()))
                .put("target", toProjectAndRepositoryMap(pullRequest.getToRef().getRepository()))
                .build();
    }
}