package com.atlassian.stash.internal.repository.sync;

import com.atlassian.sal.api.executor.ThreadLocalDelegateExecutorFactory;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.GitScmConfig;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import org.junit.Test;
import org.junit.runner.RunWith;
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.assertFalse;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultRefSyncConfigTest {

    @Mock
    private ThreadLocalDelegateExecutorFactory executorFactory;
    @Mock
    private ApplicationPropertiesService propertiesService;
    @Mock
    private GitScmConfig scmConfig;

    @Test
    public void testGetCommandSize() {
        assertFalse("The ref sync property prefix should not be applied to the command size key",
                DefaultRefSyncConfig.PROP_ENVIRONMENT_COMMAND_SIZE.startsWith(DefaultRefSyncConfig.PROP_PREFIX));

        when(scmConfig.getProperty(eq(DefaultRefSyncConfig.PROP_ENVIRONMENT_COMMAND_SIZE), eq(DefaultRefSyncConfig.DEFAULT_COMMAND_SIZE)))
                .thenReturn(8192);

        assertEquals(8192, new DefaultRefSyncConfig(scmConfig, propertiesService).getCommandSize());

        verify(scmConfig).getProperty(eq(DefaultRefSyncConfig.PROP_ENVIRONMENT_COMMAND_SIZE), eq(DefaultRefSyncConfig.DEFAULT_COMMAND_SIZE));
    }

    @Test
    public void testGetFetchTimeout() {
        long actual = TimeUnit.MINUTES.toSeconds(10);

        runFetchTimeoutTest(actual, actual);
    }

    @Test
    public void testGetFetchTimeoutAppliesMinimum() {
        runFetchTimeoutTest(DefaultRefSyncConfig.MINIMUM_FETCH_TIMEOUT, 30L);
    }

    @Test
    public void testGetMergeTimeout() {
        long actual = TimeUnit.MINUTES.toSeconds(10);

        runMergeTimeoutTest(actual, actual);
    }

    @Test
    public void testGetMergeTimeoutAppliesMinimum() {
        runMergeTimeoutTest(DefaultRefSyncConfig.MINIMUM_MERGE_TIMEOUT, 30L);
    }

    @Test
    public void testGetRelativePath() {
        Repository from = mock(Repository.class);
        Repository to = mock(Repository.class);
        when(scmConfig.getRelativePath(same(from), same(to))).thenReturn("relative");

        assertEquals("relative", new DefaultRefSyncConfig(scmConfig, propertiesService).getRelativePath(from, to));

        verify(scmConfig).getRelativePath(same(from), same(to));
    }

    @Test
    public void testGetSyncDelay() {
        runSyncDelayTest(180L, 180L);
    }

    //Ensure zero is allowed, for instances that want immediate synchronization
    @Test
    public void testGetSyncDelayAllowsZero() {
        runSyncDelayTest(0L, 0L);
    }

    @Test
    public void testGetSyncDelayAppliesMinimum() {
        runSyncDelayTest(0L, -1L);
    }

    @Test
    public void testGetThreadCount() {
        runThreadCountTest(4, 4);
    }

    @Test
    public void testGetThreadCountAppliesMinimum() {
        runThreadCountTest(1, 0);
    }

    @Test
    public void testIsImpossible() {
        DefaultRefSyncConfig config = new DefaultRefSyncConfig(scmConfig, propertiesService);
        assertFalse(config.isPossible());

        verify(scmConfig).getProperty(eq(DefaultRefSyncConfig.SCM_PROP_USE_ALTERNATES), eq(true));
    }

    private void runFetchTimeoutTest(long expected, long actual) {
        when(propertiesService.getPluginProperty(eq(DefaultRefSyncConfig.PROP_FETCH_TIMEOUT), eq(DefaultRefSyncConfig.DEFAULT_FETCH_TIMEOUT)))
                .thenReturn(actual);

        assertEquals(expected, new DefaultRefSyncConfig(scmConfig, propertiesService).getFetchTimeout());

        verify(propertiesService).getPluginProperty(eq(DefaultRefSyncConfig.PROP_FETCH_TIMEOUT), eq(DefaultRefSyncConfig.DEFAULT_FETCH_TIMEOUT));
    }

    private void runMergeTimeoutTest(long expected, long actual) {
        when(propertiesService.getPluginProperty(eq(DefaultRefSyncConfig.PROP_MERGE_TIMEOUT), eq(DefaultRefSyncConfig.DEFAULT_MERGE_TIMEOUT)))
                .thenReturn(actual);

        assertEquals(expected, new DefaultRefSyncConfig(scmConfig, propertiesService).getMergeTimeout());

        verify(propertiesService).getPluginProperty(eq(DefaultRefSyncConfig.PROP_MERGE_TIMEOUT), eq(DefaultRefSyncConfig.DEFAULT_MERGE_TIMEOUT));
    }

    private void runSyncDelayTest(long expected, long actual) {
        when(propertiesService.getPluginProperty(eq(DefaultRefSyncConfig.PROP_SYNC_DELAY), eq(DefaultRefSyncConfig.DEFAULT_SYNC_DELAY)))
                .thenReturn(actual);

        assertEquals(expected, new DefaultRefSyncConfig(scmConfig, propertiesService).getSyncDelay());

        verify(propertiesService).getPluginProperty(eq(DefaultRefSyncConfig.PROP_SYNC_DELAY), eq(DefaultRefSyncConfig.DEFAULT_SYNC_DELAY));
    }

    private void runThreadCountTest(int expected, int actual) {
        when(propertiesService.getPluginProperty(eq(DefaultRefSyncConfig.PROP_THREADS), eq(DefaultRefSyncConfig.DEFAULT_THREADS)))
                .thenReturn(actual);

        assertEquals(expected, new DefaultRefSyncConfig(scmConfig, propertiesService).getThreadCount());

        verify(propertiesService).getPluginProperty(eq(DefaultRefSyncConfig.PROP_THREADS), eq(DefaultRefSyncConfig.DEFAULT_THREADS));
    }
}
