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

import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.RefChangeType;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.GitUtils;
import com.atlassian.bitbucket.scm.git.command.updateref.GitUpdateRef;
import com.atlassian.bitbucket.scm.git.command.updateref.GitUpdateRefDeleteBuilder;
import com.atlassian.bitbucket.scm.git.command.updateref.GitUpdateRefSetBuilder;
import org.junit.Test;
import org.mockito.Mock;

import static com.atlassian.bitbucket.mockito.MockitoUtils.returnsSelf;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * A note about the mocking for this test. {@code MockCallChainAnswer} <i>cannot</i> be used when the code calls into
 * methods that are on GitCommandBuilderSupport or any of the less-qualified interfaces (interfaces with methods that
 * return {@code B}). The return type it calculates ends up being wrong (due to type erasure) and the tests fail with
 * {@code ClassCastException}s.
 */
public class DiscardRefCommandTest extends AbstractRefCommandTest {

    @Mock
    private GitCommandBuilderFactory builderFactory;
    @Mock
    private GitScmCommandBuilder builder;

    @Override
    public void setup() {
        super.setup();

        when(builderFactory.builder(same(repository))).thenReturn(builder);
    }

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

        GitUpdateRefDeleteBuilder deleteBuilder = mock(GitUpdateRefDeleteBuilder.class, returnsSelf());
        doReturn(command).when(deleteBuilder).build();

        GitUpdateRef updateRef = mock(GitUpdateRef.class);
        when(updateRef.delete(eq(MASTER))).thenReturn(deleteBuilder);

        when(agent.resolveBranch(same(repository), eq(MASTER), eq(true))).thenReturn(branch);
        when(builder.updateRef()).thenReturn(updateRef);

        RefChange change = call();
        assertNotNull(change);
        assertEquals("current", change.getFromHash());
        assertEquals(MASTER, change.getRefId());
        assertEquals(GitUtils.NULL_SHA1, change.getToHash());
        assertSame(RefChangeType.DELETE, change.getType());

        verify(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        verify(builderFactory).builder(same(repository));
        verify(builder).updateRef();
        verify(deleteBuilder).author(same(user));
        verify(command).call();
        verify(updateRef).delete(eq(MASTER));
    }

    @Test
    public void testForDeleteShortCircuitsAlreadyDeleted() {
        assertNull(call());

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

    @Test
    public void testForUpdate() {
        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");

        GitUpdateRefSetBuilder setBuilder = mock(GitUpdateRefSetBuilder.class, returnsSelf());
        doReturn(command).when(setBuilder).build();

        GitUpdateRef updateRef = mock(GitUpdateRef.class);
        when(updateRef.set(eq(MASTER), eq("remote"))).thenReturn(setBuilder);

        when(agent.resolveBranch(same(repository), eq(MASTER), eq(true))).thenReturn(local);
        when(agent.resolveBranch(same(upstream), eq(MASTER), eq(true))).thenReturn(remote);
        when(builderFactory.builder(same(repository))).thenReturn(builder);
        when(builder.updateRef()).thenReturn(updateRef);

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

        verify(agent).resolveBranch(same(repository), eq(MASTER), eq(true));
        verify(agent).resolveBranch(same(upstream), eq(MASTER), eq(true));
        verify(builderFactory).builder(same(repository));
        verify(builder).updateRef();
        verify(command).call();
        verify(setBuilder).author(same(user));
        verify(updateRef).set(eq(MASTER), eq("remote"));
    }

    private RefChange call() {
        DiscardRefCommandParameters parameters = new DiscardRefCommandParameters(repository, upstream, user, MASTER);

        return new DiscardRefCommand(agent, builderFactory, parameters).call();
    }
}
