package com.atlassian.bitbucket.internal.mirroring.mirror.webhook;

import com.atlassian.bitbucket.internal.mirroring.mirror.ExternalProject;
import com.atlassian.bitbucket.internal.mirroring.mirror.ExternalRepository;
import com.atlassian.bitbucket.internal.mirroring.mirror.InternalUpstreamService;
import com.atlassian.bitbucket.repository.RefChange;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Collections;
import java.util.List;

import static org.mockito.Mockito.*;

@SuppressWarnings("unchecked")
@RunWith(MockitoJUnitRunner.class)
public class DefaultMirroringWebHookEventHandlerTest {
    private static final String UPSTREAM_ID = "upstream-id";

    @InjectMocks
    private DefaultMirroringWebHookEventHandler handler;
    @Mock
    private InternalUpstreamService upstreamService;
    @Mock
    private Validator validator;

    @Before
    public void setUp() throws Exception {
        when(validator.validate(any(), anyVararg())).thenReturn(Collections.emptySet());
    }

    @Test
    public void testNewProjectPassedThroughWhenProjectModified() throws Exception {
        ProjectModifiedWebHookEvent event = mock(ProjectModifiedWebHookEvent.class);
        ExternalProject newProject = mock(ExternalProject.class);
        when(event.getProject()).thenReturn(newProject);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        handler.handleEvent(UPSTREAM_ID, event);

        verify(upstreamService).onProjectModified(UPSTREAM_ID, newProject);
        verify(validator).validate(same(event), anyVararg());
    }

    @Test
    public void testRepositoryPassedThroughWhenRepositoryCreated() throws Exception {
        RepositoryCreatedWebHookEvent event = mock(RepositoryCreatedWebHookEvent.class);
        ExternalRepository newRepository = mock(ExternalRepository.class);
        when(event.getRepository()).thenReturn(newRepository);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        handler.handleEvent(UPSTREAM_ID, event);

        verify(upstreamService).onRepositoryCreated(UPSTREAM_ID, newRepository);
        verify(validator).validate(same(event), anyVararg());
    }

    @Test
    public void testRepositoryPassedThroughWhenRepositoryDeleted() throws Exception {
        RepositoryDeletedWebHookEvent event = mock(RepositoryDeletedWebHookEvent.class);
        ExternalRepository repository = mock(ExternalRepository.class);
        when(event.getRepository()).thenReturn(repository);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        handler.handleEvent(UPSTREAM_ID, event);

        verify(upstreamService).onRepositoryDeleted(UPSTREAM_ID, repository);
        verify(validator).validate(same(event), anyVararg());
    }

    @Test
    public void testRepositoryPassedThroughWhenRepositoryModified() throws Exception {
        RepositoryModifiedWebHookEvent event = mock(RepositoryModifiedWebHookEvent.class);
        ExternalRepository repository = mock(ExternalRepository.class);
        when(event.getRepository()).thenReturn(repository);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        handler.handleEvent(UPSTREAM_ID, event);

        verify(upstreamService).onRepositoryModified(UPSTREAM_ID, repository);
        verify(validator).validate(same(event), anyVararg());
    }

    @Test
    public void testRepositoryPassedThroughWhenRefsChanged() throws Exception {
        RepositoryRefsChangedWebHookEvent event = mock(RepositoryRefsChangedWebHookEvent.class);
        ExternalRepository repository = mock(ExternalRepository.class);
        List<RefChange> changes = ImmutableList.of(mock(RefChange.class));
        when(event.getRepository()).thenReturn(repository);
        when(event.getChanges()).thenReturn(changes);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        handler.handleEvent(UPSTREAM_ID, event);

        verify(upstreamService).onRepositoryContentsChanged(UPSTREAM_ID, repository, changes);
        verify(validator).validate(same(event), anyVararg());
    }

    @Test(expected = ConstraintViolationException.class)
    public void testValidationFailure() throws Exception {
        ConstraintViolation violation = mock(ConstraintViolation.class);
        when(validator.validate(any(), anyVararg())).thenReturn(Collections.singleton(violation));

        RepositoryRefsChangedWebHookEvent event = mock(RepositoryRefsChangedWebHookEvent.class);
        ExternalRepository repository = mock(ExternalRepository.class);
        List<RefChange> changes = ImmutableList.of(mock(RefChange.class));
        when(event.getRepository()).thenReturn(repository);
        when(event.getChanges()).thenReturn(changes);
        when(event.visit(any(WebHookEventVisitor.class))).thenAnswer(invocationOnMock ->
                ((WebHookEventVisitor) invocationOnMock.getArguments()[0]).accept(event));

        try {
            handler.handleEvent(UPSTREAM_ID, event);
        } finally {
            verify(upstreamService, never()).onRepositoryContentsChanged(UPSTREAM_ID, repository, changes);
            verify(validator).validate(same(event), anyVararg());
        }
    }
}