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

import com.atlassian.bitbucket.commit.NoSuchCommitException;
import com.atlassian.bitbucket.validation.ArgumentValidationException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.RefChangeType;
import com.atlassian.bitbucket.scm.MergeCommandParameters;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;

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

public class MergeRefCommandTest extends AbstractRefCommandTest {

    private static final String MESSAGE = "Test";

    @Mock
    private GitCommandFactory commandFactory;
    @Spy
    private I18nService i18nService = new SimpleI18nService();

    @Test
    public void test() {
        Branch local = mock(Branch.class);
        when(local.getId()).thenReturn(MASTER);
        when(local.getLatestCommit()).thenReturn("local");

        Branch remote = mock(Branch.class);
        when(remote.getId()).thenReturn(MASTER);
        when(remote.getLatestCommit()).thenReturn("remote");

        Branch merged = mock(Branch.class);
        when(merged.getId()).thenReturn(MASTER);
        when(merged.getLatestCommit()).thenReturn("merged");

        doReturn(local).when(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        doReturn(remote).when(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        doReturn(command).when(commandFactory).merge(same(repository), isNotNull(MergeCommandParameters.class));
        doReturn(merged).when(command).call();

        RefChange change = call();
        assertNotNull(change);
        assertEquals("local", change.getFromHash());
        assertEquals(MASTER, change.getRefId());
        assertEquals("merged", change.getToHash());
        assertSame(RefChangeType.UPDATE, change.getType());

        ArgumentCaptor<MergeCommandParameters> parametersCaptor = ArgumentCaptor.forClass(MergeCommandParameters.class);

        verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        verify(commandFactory).merge(same(repository), parametersCaptor.capture());
        verify(command).call();

        MergeCommandParameters parameters = parametersCaptor.getValue();
        assertNotNull(parameters);
        assertEquals(MASTER, parameters.getFromBranch());
        assertEquals("remote", parameters.getFromCommitId());
        assertEquals(MASTER, parameters.getToBranch());
        assertSame(upstream, parameters.getFromRepository());
        assertSame(user, parameters.getAuthor());
        assertEquals(MESSAGE, parameters.getMessage());
    }

    @Test
    public void testParametersDefaultMessage() {
        assertEquals("Merging in latest from upstream (KEY/upstream:refs/heads/master)",
                new MergeRefCommandParameters(repository, upstream, user, MASTER, null).getMessage());
    }

    @Test
    public void testShortCircuitsAlreadyAtSameCommit() {
        Branch branch = mock(Branch.class);
        when(branch.getId()).thenReturn(MASTER);
        when(branch.getLatestCommit()).thenReturn("latest");

        doReturn(branch).when(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        doReturn(branch).when(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));

        assertNull(call());

        verify(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        verifyZeroInteractions(commandFactory);
    }

    @Test
    public void testSpoofsRefChangeToHashWhenCommandReturnsNull() {
        Branch local = mock(Branch.class);
        when(local.getId()).thenReturn(MASTER);
        when(local.getLatestCommit()).thenReturn("local");

        Branch remote = mock(Branch.class);
        when(remote.getId()).thenReturn(MASTER);
        when(remote.getLatestCommit()).thenReturn("remote");

        doReturn(local).when(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        doReturn(remote).when(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        doReturn(command).when(commandFactory).merge(same(repository), isNotNull(MergeCommandParameters.class));

        RefChange change = call();
        assertNotNull(change);
        assertEquals("local", change.getFromHash());
        assertEquals(MASTER, change.getRefId());
        assertEquals("remote", change.getToHash());
        assertSame(RefChangeType.UPDATE, change.getType());

        ArgumentCaptor<MergeCommandParameters> parametersCaptor = ArgumentCaptor.forClass(MergeCommandParameters.class);

        verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        verify(commandFactory).merge(same(repository), parametersCaptor.capture());
        verify(command).call();

        MergeCommandParameters parameters = parametersCaptor.getValue();
        assertNotNull(parameters);
        assertEquals(MASTER, parameters.getFromBranch());
        assertEquals("remote", parameters.getFromCommitId());
        assertEquals(MASTER, parameters.getToBranch());
        assertSame(upstream, parameters.getFromRepository());
        assertSame(user, parameters.getAuthor());
        assertEquals(MESSAGE, parameters.getMessage());
    }

    @Test(expected = NoSuchCommitException.class)
    public void testWithNonexistentRef() {
        try {
            call();
        } finally {
            verify(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
            verify(i18nService).createKeyedMessage(eq("bitbucket.service.repository.refsync.merge.nosuchbranch"), anyVararg());
            verifyZeroInteractions(commandFactory);
        }
    }

    @Test(expected = NoSuchCommitException.class)
    public void testWithOrphanedRef() {
        when(agent.resolveBranch(same(repository), eq(MASTER), eq(true))).thenReturn(mock(Branch.class));

        try {
            call();
        } finally {
            verify(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
            verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
            verify(i18nService).createKeyedMessage(eq("bitbucket.service.repository.refsync.merge.nothingupstream"), anyVararg());
            verifyZeroInteractions(commandFactory);
        }
    }

    @Test(expected = ArgumentValidationException.class)
    public void testWithTag() {
        try {
            callFor("refs/tags/some-tag");
        } finally {
            verify(i18nService).createKeyedMessage(eq("bitbucket.service.repository.refsync.merge.tag"));
            verifyZeroInteractions(agent);
            verifyZeroInteractions(commandFactory);
        }
    }

    private RefChange call() {
        return callFor(MASTER);
    }

    private RefChange callFor(String refId) {
        MergeRefCommandParameters parameters = new MergeRefCommandParameters(repository, upstream, user, refId, MESSAGE);

        return new MergeRefCommand(agent, commandFactory, i18nService, parameters).call();
    }
}
