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

import com.atlassian.bitbucket.concurrent.BucketedExecutor;
import com.atlassian.bitbucket.concurrent.BucketedExecutorSettings;
import com.atlassian.bitbucket.concurrent.ConcurrencyService;
import com.atlassian.bitbucket.event.repository.RepositoryRefsChangedEvent;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.GitScm;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.concurrent.TimeUnit;

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

@RunWith(MockitoJUnitRunner.class)
public class GitGarbageCollectionSchedulerTest {

    private static final long DELAY = 5L;
    private static final String REPO_HIERARCHY_ID = "hierarchy";
    private static final int REPO_ID = 7;

    @Mock
    private ConcurrencyService concurrencyService;
    @Mock
    private InternalGitScmConfig config;
    @Mock
    private RepositoryRefsChangedEvent event;
    @Mock
    private BucketedExecutor<GarbageCollectionRequest> executor;
    @Mock
    private Repository repository;
    @Captor
    private ArgumentCaptor<GarbageCollectionRequest> requestCaptor;
    @Mock
    private GitGarbageTruck garbageTruck;
    private GitGarbageCollectionScheduler scheduler;

    @Before
    public void setup() {
        //noinspection unchecked
        when(concurrencyService.<GarbageCollectionRequest>getBucketedExecutor(
                eq(GitGarbageCollectionScheduler.EXECUTOR_NAME), any(BucketedExecutorSettings.class)))
                .thenReturn(executor);
        when(config.getGcDelayInSeconds()).thenReturn(DELAY);
        when(event.getRepository()).thenReturn(repository);
        when(repository.getScmId()).thenReturn(GitScm.ID);
        when(repository.getHierarchyId()).thenReturn(REPO_HIERARCHY_ID);
        when(repository.getId()).thenReturn(REPO_ID);

        scheduler = new GitGarbageCollectionScheduler(config, concurrencyService, garbageTruck);
    }

    @Test
    public void testBucketIdFunctionReturnsHierarchyId() {
        ArgumentCaptor<BucketedExecutorSettings> settingsCaptor = ArgumentCaptor.forClass(BucketedExecutorSettings.class);
        verify(concurrencyService).getBucketedExecutor(
                eq(GitGarbageCollectionScheduler.EXECUTOR_NAME),
                settingsCaptor.capture());

        //noinspection unchecked
        BucketedExecutorSettings<GarbageCollectionRequest> settings = settingsCaptor.getValue();
        assertNotNull(settings);
        assertEquals("hierarchy-id",
                settings.getBucketIdExtractor().apply(new GarbageCollectionRequest(99, "hierarchy-id")));
    }

    @Test
    public void testNotScheduledIfAutoGcEnabled() {
        when(garbageTruck.shouldGc(repository)).thenReturn(false);

        scheduler.onRefsChanged(event);

        verifyZeroInteractions(executor);
    }

    @Test
    public void testNotScheduledIfRepositoryIsNotGit() {
        when(garbageTruck.shouldGc(repository)).thenReturn(true);
        when(repository.getScmId()).thenReturn("not-git");

        scheduler.onRefsChanged(event);

        verifyZeroInteractions(executor);
    }

    @Test
    public void testScheduledIfShouldGc() {
        when(garbageTruck.shouldGc(repository)).thenReturn(true);

        scheduler.onRefsChanged(event);

        verify(executor).schedule(requestCaptor.capture(), eq(DELAY), eq(TimeUnit.SECONDS));

        GarbageCollectionRequest request = requestCaptor.getValue();
        assertEquals(REPO_HIERARCHY_ID, request.getHierarchyId());
        assertEquals(REPO_ID, request.getRepositoryId());
    }

    @Test
    public void testSchedulingExceptionHandled() {
        when(garbageTruck.shouldGc(repository)).thenReturn(true);
        doThrow(new IllegalStateException()).when(executor)
                .schedule(any(GarbageCollectionRequest.class), anyLong(), any(TimeUnit.class));

        scheduler.onRefsChanged(event);

        verify(executor).schedule(requestCaptor.capture(), eq(DELAY), eq(TimeUnit.SECONDS));
    }

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

        verify(executor).shutdown();
    }
}