package com.atlassian.bitbucket.internal.defaultreviewers.rest;

import com.atlassian.bitbucket.internal.defaultreviewers.PullRequestConditionService;
import com.atlassian.bitbucket.internal.defaultreviewers.matcher.AnyRefMatcher;
import com.atlassian.bitbucket.internal.defaultreviewers.matcher.AnyRefMatcherProvider;
import com.atlassian.bitbucket.internal.defaultreviewers.model.DefaultPullRequestCondition;
import com.atlassian.bitbucket.internal.defaultreviewers.model.PullRequestCondition;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.repository.ref.restriction.RefMatcher;
import com.atlassian.bitbucket.repository.ref.restriction.RefMatcherProviderRegistry;
import com.atlassian.bitbucket.rest.enrich.AvatarEnricher;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.TestApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.sun.jersey.spi.container.ContainerRequest;
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.ws.rs.core.Response;
import java.util.Collections;
import java.util.Set;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class DefaultReviewersResourceTest {

    private static final String SOURCE_BRANCH = "refs/heads/source-branch";
    private static final String TARGET_BRANCH = "refs/heads/target-branch";
    private static final int REPO_ID = 1;
    private static final String INVALID_BRANCH = "invalid-branch";

    private final Repository repository = mock(Repository.class);
    private final Ref sourceRef = mock(Ref.class);
    private final Ref targetRef = mock(Ref.class);

    private final ApplicationUser user = new TestApplicationUser("user", 1);
    private final Set<ApplicationUser> userSet = Collections.singleton(user);
    private final PageRequest pageRequest = PageUtils.newRequest(0, 100);
    private final RefMatcher sourceMatcher = AnyRefMatcher.getInstance();
    private final RefMatcher targetMatcher = AnyRefMatcher.getInstance();
    private final PullRequestCondition pullRequestCondition = DefaultPullRequestCondition.builder()
            .id(1)
            .repository(repository)
            .sourceMatcher(sourceMatcher)
            .targetMatcher(targetMatcher)
            .reviewers(userSet)
            .requiredApprovals(1)
            .build();

    private final ContainerRequest containerRequest = mock(ContainerRequest.class);

    @Mock
    private AvatarEnricher avatarEnricher;

    @Mock
    private PullRequestConditionService pullRequestConditionService;

    @Mock
    private RefMatcherProviderRegistry refMatcherProviderRegistry;

    @Mock
    private RefService refService;

    @Mock
    private RepositoryService repositoryService;

    @Mock
    private UserService userService;

    @InjectMocks
    private DefaultReviewersResource resource;

    @Before
    public void setup() {
        when(refMatcherProviderRegistry.getProvider(anyString())).thenReturn(new AnyRefMatcherProvider());

        when(repositoryService.getById(REPO_ID)).thenReturn(repository);

        when(refService.resolveRef(any(ResolveRefRequest.class))).thenAnswer(invocation -> {
            ResolveRefRequest refRequest = (ResolveRefRequest) invocation.getArguments()[0];
            if (refRequest.getRefId().get().equals(SOURCE_BRANCH)) {
                return sourceRef;
            } else if (refRequest.getRefId().get().equals(TARGET_BRANCH)) {
                return targetRef;
            }
            return null;
        });

        when(pullRequestConditionService.getReviewers(eq(repository), eq(sourceRef), eq(targetRef)))
                .thenReturn(userSet);
        when(pullRequestConditionService.getPullRequestConditions(eq(repository), eq(pageRequest)))
                .thenReturn(PageUtils.createPage(ImmutableList.of(pullRequestCondition), pageRequest));
        when(pullRequestConditionService.createPullRequestCondition(eq(repository), any(RefMatcher.class), any(RefMatcher.class),
                anyCollectionOf(ApplicationUser.class), anyInt()))
                .thenReturn(pullRequestCondition);
        when(pullRequestConditionService.updatePullRequestCondition(anyInt(), eq(repository), any(RefMatcher.class),
                any(RefMatcher.class), anyCollectionOf(ApplicationUser.class), anyInt()))
                .thenReturn(pullRequestCondition);

        when(containerRequest.isSecure()).thenReturn(true);
    }

    @Test
    public void testGetReviewersReturnsListOfReviewers() {
        Response response = resource.getReviewers(REPO_ID, REPO_ID, SOURCE_BRANCH, TARGET_BRANCH, containerRequest);

        assertThat("Response should be 200 OK", response.getStatus(), equalTo(200));
        verify(pullRequestConditionService).getReviewers(eq(repository), eq(sourceRef), eq(targetRef));
    }

    @Test
    public void testGetReviewersWithInvalidBranchThrowsBadRequest() {
        Response response = resource.getReviewers(REPO_ID, REPO_ID, INVALID_BRANCH, TARGET_BRANCH, containerRequest);

        assertThat("Response should be 400 Bad Request", response.getStatus(), equalTo(400));
        verifyZeroInteractions(pullRequestConditionService);
    }

    @Test
    public void testGetPullRequestConditionsReturnsListOfConditions() {
        Response response = resource.getPullRequestConditions(repository, pageRequest, containerRequest);

        assertThat("Response should be 200 OK", response.getStatus(), equalTo(200));
        verify(pullRequestConditionService).getPullRequestConditions(eq(repository), eq(pageRequest));
    }

    @Test
    public void testCreatePullRequestConditionReturnsCondition() {
        RestDefaultReviewersRequest createRequest = new RestDefaultReviewersRequest();
        createRequest.put("sourceMatcher", new RestRefMatcher(sourceMatcher));
        createRequest.put("targetMatcher", new RestRefMatcher(targetMatcher));
        createRequest.put("reviewers", Lists.newArrayList());

        Response response = resource.createPullRequestCondition(repository, createRequest, containerRequest);

        assertThat("Response should be 200 OK", response.getStatus(), equalTo(200));
        verify(pullRequestConditionService).createPullRequestCondition(eq(repository), any(RefMatcher.class), any(RefMatcher.class),
                anyCollectionOf(ApplicationUser.class), anyInt());
    }

    @Test
    public void testUpdatePullRequestConditionReturnsCondition() {
        RestDefaultReviewersRequest updateRequest = new RestDefaultReviewersRequest();
        updateRequest.put("sourceMatcher", new RestRefMatcher(sourceMatcher));
        updateRequest.put("targetMatcher", new RestRefMatcher(targetMatcher));
        updateRequest.put("reviewers", Lists.newArrayList());

        Response response = resource.updatePullRequestCondition(repository, 1, updateRequest, containerRequest);

        assertThat("Response should be 200 OK", response.getStatus(), equalTo(200));
        verify(pullRequestConditionService).updatePullRequestCondition(eq(1), eq(repository), any(RefMatcher.class),
                any(RefMatcher.class), anyCollectionOf(ApplicationUser.class), anyInt());
    }

    @Test
    public void testDeletePullRequestConditionReturnsOK() {
        Response response = resource.deletePullRequestCondition(repository, 1);

        assertThat("Response should be 200 OK", response.getStatus(), equalTo(200));
        verify(pullRequestConditionService).removePullRequestCondition(eq(repository), eq(1));
    }

    @Test
    public void testDeletePullRequestConditionWithInvalidArgsReturnsBadRequest() {
        doThrow(new IllegalArgumentException("Bad request"))
                .when(pullRequestConditionService)
                .removePullRequestCondition(eq(repository), eq(1));

        Response response = resource.deletePullRequestCondition(repository, 1);

        assertThat("Response should be 400 Bad Request", response.getStatus(), equalTo(400));
    }
}