package com.atlassian.bitbucket.internal.branch;

import com.atlassian.bitbucket.branch.model.BranchClassifier;
import com.atlassian.bitbucket.branch.model.BranchModel;
import com.atlassian.bitbucket.branch.model.BranchModelService;
import com.atlassian.bitbucket.internal.branch.automerge.AutoMerge;
import com.atlassian.bitbucket.internal.branch.automerge.AutoMergeService;
import com.atlassian.bitbucket.internal.branch.automerge.AutoMergeStatus;
import com.atlassian.bitbucket.internal.branch.automerge.SimpleAutoMerge;
import com.atlassian.bitbucket.internal.branch.model.BranchTypes;
import com.atlassian.bitbucket.internal.branch.model.SimpleBranchType;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestMergeability;
import com.atlassian.bitbucket.pull.PullRequestRef;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.SimpleBranch;
import com.atlassian.bitbucket.user.DummySecurityService;
import com.atlassian.bitbucket.user.SecurityService;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.Arrays;
import java.util.Map;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MergeConflictPropertyProviderTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    private AutoMergeService autoMergeService;
    @Mock
    private BranchModelService branchModelService;
    @Spy
    private SecurityService securityService = new DummySecurityService();

    @InjectMocks
    private MergeConflictPropertyProvider provider;

    @Mock
    private BranchModel branchModel;
    @Mock
    private BranchClassifier classifier;
    @Mock
    private PullRequestRef fromRef;
    @Mock
    private PullRequestRef toRef;
    @Mock
    private PullRequest pullRequest;
    @Mock
    private Repository repository;

    private String fromRefId = "refs/heads/from";
    private Branch fromBranch = branch(fromRefId);
    private String toRefId = "refs/heads/to";
    private Branch toBranch = branch(toRefId);

    @Before
    public void setup() {
        when(autoMergeService.getAutoMerge(repository, fromRef)).thenReturn(autoMergeDisabled());
        when(branchModel.getClassifier()).thenReturn(classifier);
        when(branchModelService.getModel(repository)).thenReturn(branchModel);

        when(fromRef.getRepository()).thenReturn(repository);
        when(fromRef.getId()).thenReturn(fromRefId);
        when(fromRef.getDisplayId()).thenReturn("from");
        when(toRef.getRepository()).thenReturn(repository);
        when(toRef.getId()).thenReturn(toRefId);
        when(toRef.getDisplayId()).thenReturn("to");
        when(pullRequest.getFromRef()).thenReturn(fromRef);
        when(pullRequest.getToRef()).thenReturn(toRef);
    }

    @Test
    public void testNoOpIfThePullRequestCanBeMerged() {
        assertThat(provide(true, false).keySet(), hasSize(0));
    }

    @Test
    public void testNoOpIfThePullRequestMergeIsNotConflicted() {
        assertThat(provide(false, false).keySet(), hasSize(0));
    }

    @Test
    public void testNoOpIfTheBranchingModelIsDisabled() {
        when(branchModelService.getModel(repository)).thenReturn(null);
        Map<String, Object> properties = provide(false, true);
        assertThat(properties.keySet(), hasSize(0));
    }

    @Test
    public void testSetTheAutoMergeConflictFlagForAutoMergeConflicts() {
        when(autoMergeService.getAutoMerge(same(repository), same(fromRef))).thenReturn(autoMergeForPullRequest());

        Map<String, Object> properties = provide(false, true);

        assertThat(properties.get(MergeConflictPropertyProvider.PROP_AUTO_MERGE_CONFLICT), is(true));
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("stable"));
        assertThat(properties.keySet(), hasSize(2));
    }

    @Test
    public void testSetSourceRefStabilityToStableForAReleaseBranch() {
        when(classifier.getType(eq(branch(fromRefId)))).thenReturn(new SimpleBranchType(BranchTypes.RELEASE, fromRefId));

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("stable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testSetSourceRefStabilityToStableForAProductionBranch() {
        when(classifier.getType(eq(branch(fromRefId)))).thenReturn(null);
        when(branchModel.getProduction()).thenReturn(fromBranch);

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("stable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testSetSourceRefStabilityToStableForADevelopmentBranch() {
        when(branchModel.getDevelopment()).thenReturn(fromBranch);

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("stable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testSetSourceRefStabilityTounstableForABugfixBranch() {
        when(classifier.getType(eq(branch(fromRefId)))).thenReturn(new SimpleBranchType(BranchTypes.BUGFIX, fromRefId));

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("unstable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testSetSourceRefStabilityTounstableForAHotfixBranch() {
        when(classifier.getType(eq(branch(fromRefId)))).thenReturn(new SimpleBranchType(BranchTypes.HOTFIX, fromRefId));

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("unstable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testSetSourceRefStabilityTounstableForAFeatureBranch() {
        when(classifier.getType(eq(branch(fromRefId)))).thenReturn(new SimpleBranchType(BranchTypes.FEATURE, fromRefId));

        Map<String, Object> properties = provide(false, true);
        assertThat(properties.get(MergeConflictPropertyProvider.PROP_SOURCE_BRANCH_STABILITY), is("unstable"));
        assertThat(properties.keySet(), hasSize(1));
    }

    @Test
    public void testNotSetSourceRefStabilityToUnstableForAnUnclassifiedBranch() {
        Map<String, Object> properties = provide(false, true);
        assertThat(properties.keySet(), hasSize(0));
    }

    private AutoMerge autoMergeDisabled() {
        return SimpleAutoMerge.skip(AutoMergeStatus.AUTO_MERGE_DISABLED, fromBranch);
    }

    private AutoMerge autoMergeForPullRequest() {
        return SimpleAutoMerge.proceed(Arrays.asList(fromBranch, toBranch));
    }

    private Map<String, Object> provide(boolean canMerge, boolean conflicted) {
        return provider.provideProperties(pullRequest, mockMergeability(canMerge, conflicted));
    }

    private PullRequestMergeability mockMergeability(boolean canMerge, boolean conflicted) {
        PullRequestMergeability mergeability = mock(PullRequestMergeability.class);
        when(mergeability.canMerge()).thenReturn(canMerge);
        when(mergeability.isConflicted()).thenReturn(conflicted);
        return mergeability;
    }

    private Branch branch(String id) {
        return new SimpleBranch.Builder()
                .id(id)
                .displayId(id)
                .build();
    }
}
