package com.atlassian.stash.internal.build.rest;

import com.atlassian.bitbucket.build.BuildState;
import com.atlassian.bitbucket.build.BuildSummary;
import com.atlassian.bitbucket.build.BuildStatus;
import com.atlassian.bitbucket.build.BuildStatusService;
import com.atlassian.bitbucket.rest.RestMapEntity;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
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.ArrayList;
import java.util.Collection;

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

@RunWith(MockitoJUnitRunner.class)
public class BuildStatusResourceTest {

    private static final String COMMIT_ID = "24625557483f26cdf32350979ceb2287e4d50654";

    @Mock
    private BuildStatusService service;

    @InjectMocks
    private BuildStatusResource resource;

    @Test
    public void testGetBuildStats() throws Exception {
        BuildSummary stats = createStats(0, 1, 3);
        when(service.getSummary(eq(COMMIT_ID))).thenReturn(stats);

        Response response = resource.getBuildStatusStats(COMMIT_ID, false);
        assertStats(response, 0, 1, 3, null);
    }

    @Test
    public void testGetBuildStatsAndIncludeUnique() throws Exception {
        BuildSummary stats = createStats(0, 1, 3);
        when(service.getSummary(eq(COMMIT_ID))).thenReturn(stats);

        // include a failed result
        doReturn(createPage(BuildState.INPROGRESS)).when(service).findAll(eq(COMMIT_ID));
        Response response = resource.getBuildStatusStats(COMMIT_ID, true);
        assertStats(response, 0, 1, 3, BuildState.INPROGRESS);

        // include a successful result
        doReturn(createPage(BuildState.SUCCESSFUL)).when(service).findAll(eq(COMMIT_ID));
        response = resource.getBuildStatusStats(COMMIT_ID, true);
        assertStats(response, 0, 1, 3, null);
    }

    @Test
    public void testIncludeUniqueResult() throws Exception {
        doReturn(createPage(BuildState.values())).when(service).findAll(eq(COMMIT_ID));

        BuildSummary stats = createStats(0, 0, 0);
        RestBuildStats restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));

        stats = createStats(1, 1, 1);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats).include(buildStatus(BuildState.FAILED));

        stats = createStats(1, 2, 3);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats).include(buildStatus(BuildState.FAILED));

        stats = createStats(2, 1, 1);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));

        stats = createStats(2, 1, 2);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));

        stats = createStats(2, 2, 1);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));

        stats = createStats(0, 2, 3);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));

        stats = createStats(0, 1, 3);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats).include(buildStatus(BuildState.INPROGRESS));

        stats = createStats(0, 1, 0);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats).include(buildStatus(BuildState.INPROGRESS));

        stats = createStats(0, 0, 1);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats).include(buildStatus(BuildState.SUCCESSFUL));

        stats = createStats(0, 0, 2);
        restStats = mock(RestBuildStats.class);
        resource.includeUniqueResult(restStats, COMMIT_ID, stats);
        verify(restStats, never()).include(notNull(BuildStatus.class));
    }

    private BuildSummary createStats(int failed, int inProgress, int successful) {
        BuildSummary mockStats = mock(BuildSummary.class);
        when(mockStats.getFailedCount()).thenReturn(failed);
        when(mockStats.getInProgressCount()).thenReturn(inProgress);
        when(mockStats.getSuccessfulCount()).thenReturn(successful);
        return mockStats;
    }

    private Page<BuildStatus> createPage(BuildState... states) {
        Collection<BuildStatus> statuses = new ArrayList<>(states.length);
        for (BuildState state : states) {
            BuildStatus status = mock(BuildStatus.class);
            when(status.getState()).thenReturn(state);
            statuses.add(status);
        }
        return PageUtils.createPage(statuses, PageUtils.newRequest(0, 10));
    }

    private void assertStats(Response response, int failed, int inProgress, int successful, BuildState state) {
        assertEquals(failed, ((RestMapEntity) response.getEntity()).get("failed"));
        assertEquals(inProgress, ((RestMapEntity) response.getEntity()).get("inProgress"));
        assertEquals(successful, ((RestMapEntity) response.getEntity()).get("successful"));

        RestBuildStatus[] results = (RestBuildStatus[]) ((RestMapEntity) response.getEntity()).get("results");
        if (state == null) {
            assertNull(results);
        } else {
            assertNotNull(results);
            assertEquals(1, results.length);
            assertEquals(state, results[0].getState());
        }
    }

    private BuildStatus buildStatus(final BuildState state) {
        return argThat(new TypeSafeMatcher<BuildStatus>() {

            @Override
            protected boolean matchesSafely(BuildStatus status) {
                return status.getState() == state;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("Build status with state ").appendValue(state);
            }
        });
    }

}
