package com.atlassian.stash.internal.hook;

import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.module.ModuleFactory;
import com.atlassian.bitbucket.hook.BaseScmHookModuleDescriptor;
import com.atlassian.bitbucket.hook.PostReceiveHook;
import com.atlassian.bitbucket.hook.PostReceiveHookModuleDescriptor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Collections;
import java.util.List;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DefaultBuiltInHookHandlerFactoryTest {

    @Mock
    private PluginAccessor pluginAccessor;

    @Mock
    private ModuleFactory moduleFactory;

    @InjectMocks
    private DefaultBuiltInHookHandlerFactory factory;

    @Test
    public void testKeyNullComesLast() throws Exception {
        Iterable<PostReceiveHook> enabledModules = createModuleList(
                moduleDescriptorWithKey(null),
                moduleDescriptorWithKey("Z")
        );

        assertPositionMatches(enabledModules, 0, containsString("Key: Z"));
        assertPositionMatches(enabledModules, 1, containsString("Key: null"));
    }

    @Test
    public void testKeyCaseInsensitive() throws Exception {
        Iterable<PostReceiveHook> enabledModules = createModuleList(
                moduleDescriptorWithKey("z"),
                moduleDescriptorWithKey("a"),
                moduleDescriptorWithKey("k"),
                moduleDescriptorWithKey("B")
        );

        assertPositionMatches(enabledModules, 0, containsString("Key: a"));
        assertPositionMatches(enabledModules, 1, containsString("Key: B"));
        assertPositionMatches(enabledModules, 2, containsString("Key: k"));
        assertPositionMatches(enabledModules, 3, containsString("Key: z"));
    }

    @Test
    public void testWeight() throws Exception {
        Iterable<PostReceiveHook> enabledModules = createModuleList(
                moduleDescriptorWithWeight(2300),
                moduleDescriptorWithWeight(5),
                moduleDescriptorWithWeight(2301),
                moduleDescriptorWithWeight(11)
        );

        assertPositionMatches(enabledModules, 0, containsString("weight: 5"));
        assertPositionMatches(enabledModules, 1, containsString("weight: 11"));
        assertPositionMatches(enabledModules, 2, containsString("weight: 2300"));
        assertPositionMatches(enabledModules, 3, containsString("weight: 2301"));
    }

    @Test
    public void testGetEnabledModulesSortByWeightThenModuleKey() throws Exception {
        Iterable<PostReceiveHook> enabledModules = createModuleList(
                moduleDescriptorWithKeyAndWeight("Z", 10),
                moduleDescriptorWithKeyAndWeight("k", 50),
                moduleDescriptorWithKeyAndWeight(null, 50),
                moduleDescriptorWithKeyAndWeight("A", 50),
                moduleDescriptorWithKeyAndWeight("a", 20),
                moduleDescriptorWithKeyAndWeight("B", 20)
        );

        assertPositionMatches(enabledModules, 0, equalTo("Key: Z, weight: 10"));
        assertPositionMatches(enabledModules, 1, equalTo("Key: a, weight: 20"));
        assertPositionMatches(enabledModules, 2, equalTo("Key: B, weight: 20"));
        assertPositionMatches(enabledModules, 3, equalTo("Key: A, weight: 50"));
        assertPositionMatches(enabledModules, 4, equalTo("Key: k, weight: 50"));
        assertPositionMatches(enabledModules, 5, equalTo("Key: null, weight: 50"));
    }

    private static void assertPositionMatches(Iterable<PostReceiveHook> enabledModules, int position, Matcher<String> matcher) {
        assertThat(Iterables.get(enabledModules, position).toString(), matcher);
    }

    private <T extends BaseScmHookModuleDescriptor<M>, M> Iterable<M> createModuleList(T... descriptors) {
        List<T> moduleDescriptors = ImmutableList.copyOf(descriptors);
        if (moduleDescriptors.isEmpty()) {
            return Collections.emptyList();
        }
        Class<T> clazz = (Class<T>) Iterables.getLast(moduleDescriptors).getClass();
        when(pluginAccessor.getEnabledModuleDescriptorsByClass(clazz)).thenReturn(moduleDescriptors);
        Iterable<M> enabledModules = factory.getEnabledModules(clazz);
        assertThat(Iterables.size(enabledModules), is(moduleDescriptors.size()));
        return enabledModules;
    }

    private PostReceiveHookModuleDescriptor moduleDescriptorWithWeight(int weight) {
        return moduleDescriptorWithKeyAndWeight("", weight);
    }

    private PostReceiveHookModuleDescriptor moduleDescriptorWithKey(String moduleKey) {
        return moduleDescriptorWithKeyAndWeight(moduleKey, 1000);
    }

    private PostReceiveHookModuleDescriptor moduleDescriptorWithKeyAndWeight(final String moduleKey, final int weight) {
        return new PostReceiveHookModuleDescriptor(moduleFactory) {
            @Override
            public PostReceiveHook getModule() {
                PostReceiveHook hook = mock(PostReceiveHook.class);
                when(hook.toString()).thenReturn("Key: " + moduleKey + ", weight: " + weight);
                return hook;
            }

            @Override
            public String getCompleteKey() {
                return moduleKey;
            }

            @Override
            public int getWeight() {
                return weight;
            }
        };
    }
}