package com.atlassian.bitbucket.mesh.git.pull;

import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.repository.BitbucketServerRepository;
import com.atlassian.bitbucket.mesh.repository.ManagedRepository;
import com.atlassian.bitbucket.mesh.transaction.ReferenceTransaction;
import com.atlassian.bitbucket.mesh.transaction.ReferenceTransactionState;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DarkReferenceTransactionListenerTest {

    @Mock
    private GitAgent agent;
    @Mock
    private BitbucketServerRepository bitbucketServerRepository;
    @InjectMocks
    private DarkReferenceTransactionListener listener;
    @Mock
    private ManagedRepository managedRepository;
    @Mock
    private ReferenceTransaction transaction;

    @Test
    public void testOnReferenceTransactionEnablesReflogsForNewDarkRefsForBitbucketServerRepository() {
        RefUpdate publicFrom = RefUpdate.ofAdd("refs/pull-requests/1/from", "1-to");
        RefUpdate darkFrom = RefUpdate.ofAdd("stash-refs/pull-requests/1/from", "1-to");

        when(transaction.getRefUpdates()).thenReturn(ImmutableSet.of(publicFrom, darkFrom));
        when(transaction.getRepository()).thenReturn(bitbucketServerRepository);
        when(transaction.getState()).thenReturn(ReferenceTransactionState.PREPARED);

        listener.onReferenceTransaction(transaction);

        verify(agent).enableReflog(same(bitbucketServerRepository), eq(darkFrom.getRefId()));
    }

    @Test
    public void testOnReferenceTransactionEnablesReflogsForNewAndUpdatedDarkRefsForManagedRepository() {
        RefUpdate publicFrom = RefUpdate.ofAdd("refs/pull-requests/1/from", "1-to");
        RefUpdate darkFrom = RefUpdate.ofAdd("dark-refs/pull-requests/1/from", "1-to");
        RefUpdate darkFromUpdate = RefUpdate.of("dark-refs/pull-requests/2/from", "2-from", "2-to");

        when(transaction.getRefUpdates()).thenReturn(ImmutableSet.of(publicFrom, darkFrom, darkFromUpdate));
        when(transaction.getRepository()).thenReturn(managedRepository);
        when(transaction.getState()).thenReturn(ReferenceTransactionState.PREPARED);

        listener.onReferenceTransaction(transaction);

        verify(agent).enableReflog(same(managedRepository), eq(darkFrom.getRefId()));
        verify(agent).enableReflog(same(managedRepository), eq(darkFromUpdate.getRefId()));
    }

    @Test
    public void testOnReferenceTransactionIgnoresAborted() {
        when(transaction.getState()).thenReturn(ReferenceTransactionState.ABORTED);

        listener.onReferenceTransaction(transaction);

        verify(transaction).getState();
        verifyNoMoreInteractions(transaction); //This verifies the ref updates are not inspected
        verifyNoInteractions(agent);
    }

    @Test
    public void testOnReferenceTransactionIgnoresCommitted() {
        when(transaction.getState()).thenReturn(ReferenceTransactionState.COMMITTED);

        listener.onReferenceTransaction(transaction);

        verify(transaction).getState();
        verifyNoMoreInteractions(transaction); //This verifies the ref updates are not inspected
        verifyNoInteractions(agent);
    }

    @Test
    public void testOnReferenceTransactionIgnoresDarkRefDeletionsAndUpdatesForBitbucketServerRepository() {
        RefUpdate darkDelete = RefUpdate.ofDelete("stash-refs/pull-requests/1/from", "1-from");
        RefUpdate darkUpdate = RefUpdate.of("stash-refs/pull-requests/2/from", "2-from", "2-to");

        when(transaction.getRefUpdates()).thenReturn(ImmutableSet.of(darkDelete, darkUpdate));
        when(transaction.getRepository()).thenReturn(bitbucketServerRepository);
        when(transaction.getState()).thenReturn(ReferenceTransactionState.PREPARED);

        listener.onReferenceTransaction(transaction);

        verify(transaction).getState();   //State and rejected verify the preconditions are
        verify(transaction).isRejected(); //checked before refs are inspected
        verify(transaction).getRepository();
        verify(transaction).getRefUpdates(); //This verifies the refs are inspected
        verifyNoInteractions(agent);
    }

    @Test
    public void testOnReferenceTransactionIgnoresDarkRefDeletionsForManagedRepository() {
        RefUpdate darkDelete = RefUpdate.ofDelete("dark-refs/pull-requests/1/from", "1-from");

        when(transaction.getRefUpdates()).thenReturn(ImmutableSet.of(darkDelete));
        when(transaction.getRepository()).thenReturn(managedRepository);
        when(transaction.getState()).thenReturn(ReferenceTransactionState.PREPARED);

        listener.onReferenceTransaction(transaction);

        verify(transaction).getState();   //State and rejected verify the preconditions are
        verify(transaction).isRejected(); //checked before refs are inspected
        verify(transaction).getRepository();
        verify(transaction).getRefUpdates(); //This verifies the refs are inspected
        verifyNoInteractions(agent);
    }

    @Test
    public void testOnReferenceTransactionIgnoresPreparedIfAlreadyRejected() {
        when(transaction.getState()).thenReturn(ReferenceTransactionState.PREPARED);
        when(transaction.isRejected()).thenReturn(true);

        listener.onReferenceTransaction(transaction);

        verify(transaction).getState();
        verify(transaction).isRejected();
        verifyNoMoreInteractions(transaction); //This verifies the ref updates are not inspected
        verifyNoInteractions(agent);
    }
}