package com.atlassian.stash.internal.scm.git.pull.task;

import com.atlassian.event.api.EventListener;
import com.atlassian.bitbucket.concurrent.BucketedExecutor;
import com.atlassian.bitbucket.concurrent.BucketedExecutorSettings;
import com.atlassian.bitbucket.concurrent.ConcurrencyPolicy;
import com.atlassian.bitbucket.concurrent.ConcurrencyService;
import com.atlassian.bitbucket.event.pull.PullRequestDeclinedEvent;
import com.atlassian.bitbucket.event.pull.PullRequestEvent;
import com.atlassian.bitbucket.event.pull.PullRequestMergedEvent;
import com.atlassian.bitbucket.event.pull.PullRequestRescopedEvent;

import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Listens for various {@link PullRequestEvent}s and triggers {@link PullRequestTask task}s for them.
 * <p>
 * This could likely be collapsed into {@code DefaultPullRequestRefHelper}, but doing so would make testing that
 * class much more difficult. Since it's used by several Git tests, it's simpler to keep these functions separate.
 *
 * @since 3.4
 */
public class PullRequestTaskListener {

    public static final String EXECUTOR_NAME = "git:pull-request-tasks";

    private static final Function<PullRequestTask, String> TO_BUCKET_ID =
            new Function<PullRequestTask, String>() {

                @Override
                public String apply(PullRequestTask task) {
                    //Bucketed just by repository to allow tasks to be consumed more quickly. This may change if
                    //more types of tasks are added (like managing the public refs, for example)
                    return String.valueOf(task.getRepositoryId());
                }
            };

    private final BucketedExecutor<PullRequestTask> bucketedExecutor;

    public PullRequestTaskListener(ConcurrencyService concurrencyService, PullRequestTaskProcessor taskProcessor) {
        bucketedExecutor = concurrencyService.getBucketedExecutor(EXECUTOR_NAME,
                new BucketedExecutorSettings.Builder<>(TO_BUCKET_ID, taskProcessor)
                        .maxAttempts(1) //No point in multiple attempts; the next version bump will effectively retry
                        .maxConcurrency(2, ConcurrencyPolicy.PER_NODE) //Not that I/O heavy, so allow more concurrency
                        .build());
    }

    @EventListener
    public void onPullRequestDeclined(PullRequestDeclinedEvent event) {
        handleEvent(event);
    }

    @EventListener
    public void onPullRequestMerged(PullRequestMergedEvent event) {
        handleEvent(event);
    }

    @EventListener
    public void onPullRequestRescoped(PullRequestRescopedEvent event) {
        handleEvent(event);
    }

    public void shutdown() {
        bucketedExecutor.shutdown();
    }

    //Everything delegates to this, but we don't actually want to listen for all pull request events directly
    //because most of them don't affect the version
    private void handleEvent(PullRequestEvent event) {
        //Schedule the cleanup with a 5 minute delay so that there's less chance of any caller requesting any
        //of the previous version files and missing because the file has been deleted
        bucketedExecutor.schedule(new RemoveOldPullRequestVersionsTask(event.getPullRequest()), 5L, TimeUnit.MINUTES);
    }
}
