package com.atlassian.stash.internal.commit;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.SimpleCommit;
import com.atlassian.bitbucket.idx.CommitIndex;
import com.atlassian.bitbucket.idx.CommitPropertyConfiguration;
import com.atlassian.bitbucket.property.PropertyMap;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestRef;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.Person;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.stash.internal.repository.RepositoryActivityDao;
import com.atlassian.stash.internal.user.InternalUserService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
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 java.util.Collections;
import java.util.Date;

import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DatabaseCommitEnricherTest {

    @Mock
    private RepositoryActivityDao activityDao;
    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private CommitPropertyConfiguration configuration;
    @InjectMocks
    private DatabaseCommitEnricher enricher;
    @Mock
    private CommitIndex index;
    @Mock
    private PullRequest pullRequest;
    @Mock
    private Repository repository;
    @Mock
    private PullRequestRef toRef;
    @Mock
    private InternalUserService userService;

    private PropertyMap properties;
    private SimpleCommit commit;

    @Before
    public void setup() {
        when(authenticationContext.isAuthenticated()).thenReturn(true);

        when(repository.getId()).thenReturn(101);
        when(pullRequest.getId()).thenReturn(201L);

        commit = new SimpleCommit.Builder("abcdef")
                .author(mock(Person.class))
                .authorTimestamp(new Date())
                .displayId("abcdef")
                .repository(repository)
                .build();

        // CommitIndex.getProperties states that all values must be Set<String>
        properties = new PropertyMap.Builder()
                .property("one", ImmutableSet.of("1"))
                .property("two", ImmutableSet.of("2"))
                .property("extra-property", ImmutableSet.of("extra-value"))
                .build();

        when(configuration.getPropertiesToPreload(repository)).thenReturn(ImmutableList.of("extra-property"));
        when(index.getProperties(argThat(containsCommit("abcdef")), argThat(hasItems("one", "two", "three", "extra-property"))))
                .thenReturn(ImmutableMap.of("abcdef", properties));
    }

    @Test
    public void testEnrich() {
        when(configuration.getPropertiesToPreload(repository)).thenReturn(ImmutableList.of("extra-property"));
        when(index.getProperties(argThat(containsCommit("abcdef")), argThat(hasItems("one", "two", "three", "extra-property"))))
                .thenReturn(ImmutableMap.of("abcdef", properties));

        when(activityDao.countCommentsByCommit(101, Collections.singleton("abcdef")))
                .thenReturn(ImmutableMap.of("abcdef", 5L));

        assertCommitProperties(enricher.enrich(repository, commit, ImmutableList.of("one", "two", "three")), 5);
    }

    @Test
    public void testEnrichForPullRequest() {
        when(pullRequest.getToRef()).thenReturn(toRef);
        when(toRef.getRepository()).thenReturn(repository);

        when(activityDao.countCommentsByPullRequestCommit(101, 201L, Collections.singleton("abcdef")))
                .thenReturn(ImmutableMap.of("abcdef", 3L));

        assertCommitProperties(enricher.enrich(pullRequest, commit, ImmutableList.of("one", "two", "three")), 3);
    }

    @Test
    public void testEnrichPageForPullRequest() {
        when(pullRequest.getToRef()).thenReturn(toRef);
        when(toRef.getRepository()).thenReturn(repository);

        when(activityDao.countCommentsByPullRequestCommit(101, 201L, Collections.singleton("abcdef")))
                .thenReturn(ImmutableMap.of("abcdef", 3L));

        Page<Commit> resultPage = enricher.enrichPage(pullRequest,
                PageUtils.createPage(Collections.singleton(commit), PageUtils.newRequest(0, 5)),
                ImmutableList.of("one", "two", "three"));

        assertEquals(1, resultPage.getSize());
        assertCommitProperties(Iterables.getFirst(resultPage.getValues(), null), 3);
    }

    @Test
    public void testEnrichAllForPullRequest() {
        when(pullRequest.getToRef()).thenReturn(toRef);
        when(toRef.getRepository()).thenReturn(repository);

        when(activityDao.countCommentsByPullRequestCommit(101, 201L, Collections.singleton("abcdef")))
                .thenReturn(ImmutableMap.of("abcdef", 3L));

        Iterable<Commit> results = enricher.enrichAll(pullRequest, Collections.singleton(commit),
                ImmutableList.of("one", "two", "three"));

        assertEquals(1, Iterables.size(results));
        assertCommitProperties(Iterables.getFirst(results, null), 3);
    }

    private void assertCommitProperties(Commit commit, long commentCount) {
        PropertyMap expectedProperties = new PropertyMap.Builder()
                .properties(properties)
                .property("commentCount", commentCount)
                .build();
        assertNotNull(commit.getProperties());
        assertEquals(expectedProperties, commit.getProperties());
    }

    @SuppressWarnings("unchecked")
    private Matcher<Iterable<String>> containsCommit(String commitId) {
        return (Matcher) Matchers.contains(commitId);
    }
}
