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

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.PullRequestMergedEvent;
import com.atlassian.bitbucket.event.pull.PullRequestRescopedEvent;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestRef;
import com.atlassian.bitbucket.repository.Repository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class PullRequestTaskListenerTest {

    @Mock
    private BucketedExecutor<PullRequestTask> bucketedExecutor;
    @Mock
    private ConcurrencyService concurrencyService;
    private PullRequestTaskListener listener;
    @Mock
    private PullRequestTaskProcessor taskProcessor;

    @Before
    public void setup() {
        doReturn(bucketedExecutor)
                .when(concurrencyService)
                .getBucketedExecutor(eq(PullRequestTaskListener.EXECUTOR_NAME), any(BucketedExecutorSettings.class));

        listener = new PullRequestTaskListener(concurrencyService, taskProcessor);
    }

    @Test
    public void testConstruction() {
        PullRequestTask task = mock(PullRequestTask.class);
        when(task.getRepositoryId()).thenReturn(10000);

        @SuppressWarnings("unchecked")
        ArgumentCaptor<BucketedExecutorSettings<PullRequestTask>> settingsCaptor =
                (ArgumentCaptor) ArgumentCaptor.forClass(BucketedExecutorSettings.class);

        verify(concurrencyService).getBucketedExecutor(eq(PullRequestTaskListener.EXECUTOR_NAME), settingsCaptor.capture());

        BucketedExecutorSettings<PullRequestTask> settings = settingsCaptor.getValue();
        assertEquals(Integer.MAX_VALUE, settings.getBatchSize());
        assertEquals(String.valueOf(task.getRepositoryId()), settings.getBucketIdExtractor().apply(task));
        assertEquals(1, settings.getMaxAttempts());
        assertEquals(2, settings.getMaxConcurrency());
        assertSame(ConcurrencyPolicy.PER_NODE, settings.getMaxConcurrencyPolicy());
        assertSame(taskProcessor, settings.getProcessor());
    }

    @Test
    public void testOnPullRequestDeclined() throws Exception {
        listener.onPullRequestDeclined(new PullRequestDeclinedEvent(this, pullRequest()));

        verifyScheduling();
    }

    @Test
    public void testOnPullRequestMerged() throws Exception {
        listener.onPullRequestMerged(new PullRequestMergedEvent(this, pullRequest()));

        verifyScheduling();
    }

    @Test
    public void testOnPullRequestRescoped() throws Exception {
        listener.onPullRequestRescoped(new PullRequestRescopedEvent(this, pullRequest(),
                "previousFromHash", "previousToHash"));

        verifyScheduling();
    }

    @Test
    public void testShutdown() {
        listener.shutdown();

        verify(bucketedExecutor).shutdown();
    }

    private static PullRequest pullRequest() {
        Repository repository = mock(Repository.class);
        when(repository.getId()).thenReturn(1);

        PullRequestRef fromRef = mock(PullRequestRef.class);
        when(fromRef.getRepository()).thenReturn(repository);
        when(fromRef.getLatestCommit()).thenReturn("latestFromHash");

        PullRequestRef toRef = mock(PullRequestRef.class);
        when(toRef.getRepository()).thenReturn(repository);
        when(toRef.getLatestCommit()).thenReturn("latestToHash");

        PullRequest pullRequest = mock(PullRequest.class);
        when(pullRequest.getId()).thenReturn(10L);
        when(pullRequest.getToRef()).thenReturn(toRef);
        when(pullRequest.getFromRef()).thenReturn(fromRef);
        when(pullRequest.getVersion()).thenReturn(5);

        return pullRequest;
    }

    private void verifyScheduling() {
        ArgumentCaptor<PullRequestTask> taskCaptor = ArgumentCaptor.forClass(PullRequestTask.class);

        verify(bucketedExecutor).schedule(taskCaptor.capture(), eq(5L), same(TimeUnit.MINUTES));

        PullRequestTask task = taskCaptor.getValue();
        assertTrue(task instanceof RemoveOldPullRequestVersionsTask);
        assertEquals(1, task.getRepositoryId());
        assertEquals(10L, task.getPullRequestId());

        RemoveOldPullRequestVersionsTask realTask = (RemoveOldPullRequestVersionsTask) task;
        assertEquals(5, realTask.getVersion());
    }
}