package com.atlassian.stash.internal.comment.like.dao;

import com.atlassian.activeobjects.test.TestActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDaoTest;
import com.atlassian.stash.internal.comment.like.config.CommentLikeConfig;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import net.java.ao.test.jdbc.Data;
import org.junit.Before;
import org.junit.Test;

import java.util.Collection;
import java.util.Map;

import static com.atlassian.stash.internal.comment.like.dao.CommentLikeTestData.*;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.size;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@Data(CommentLikeTestData.class)
@SuppressWarnings("unchecked")
public class AoCommentLikeDaoTest extends AbstractAoDaoTest {

    /**
     * This affects how many elements will the generated IN clause for the findByCommentIds contain. We lower it
     * significantly to test that the DAO correctly generates multiple queries when invoked with a large list of
     * comment IDs.
     *
     * @see #testFindByCommentIdsManyCommentIds()
     */
    private static final int TEST_IN_CLAUSE_LIMIT = 5;

    private CommentLikeConfig commentLikeConfig;

    private AoCommentLikeDao dao;

    public AoCommentLikeDaoTest() {
        super(AoCommentLike.class);
    }

    @Before
    public void setUpDao() {
        commentLikeConfig = mock(CommentLikeConfig.class);
        dao = new AoCommentLikeDao(new TestActiveObjects(entityManager), commentLikeConfig, TEST_IN_CLAUSE_LIMIT);

        when(commentLikeConfig.getMaxResources()).thenReturn(500);
    }

    @Test
    public void testCreateNewLike() {
        long likeId = dao.create(100L, 100);
        assertThat(likeId, greaterThan(0L));

        assertTrue(dao.exists(100L, 100));
    }

    @Test
    public void testDeleteLike() {
        dao.create(100L, 100);
        assertTrue(dao.exists(100L, 100));

        dao.delete(100L, 100);
        assertFalse("Like should have been deleted", dao.exists(100L, 100));
    }

    @Test
    public void testDeleteByCommentId() {
        dao.create(100L, 100);
        dao.create(100L, 101);
        dao.create(100L, 102);

        dao.deleteByCommentId(100L);

        assertThat(dao.findByCommentId(100L), emptyIterable());
    }

    @Test
    public void testExists() {
        // should find
        assertTrue(dao.exists(LIKED_COMMENT_1_ID, COMMENT_1_LIKER_1_ID));

        // should not find
        assertFalse(dao.exists(LIKED_COMMENT_1_ID, 50));
    }

    @Test
    public void testExistsDuplicateLikeEntity() {
        // create a duplicate row
        dao.create(LIKED_COMMENT_1_ID, COMMENT_1_LIKER_1_ID);

        // should still work
        assertTrue(dao.exists(LIKED_COMMENT_1_ID, COMMENT_1_LIKER_1_ID));
    }

    @Test
    public void testRetainExisting() {
        Map<Long, Collection<Integer>> check1 = ImmutableMap.<Long, Collection<Integer>>of(
                LIKED_COMMENT_1_ID, ImmutableSet.of(COMMENT_1_LIKER_1_ID));

        Map<Long, Collection<Integer>> retained1 = dao.retainExisting(check1);
        assertThat(retained1.size(), equalTo(1));
        assertThat(retained1, hasKey(LIKED_COMMENT_1_ID));
        assertThat(retained1.get(LIKED_COMMENT_1_ID), contains(COMMENT_1_LIKER_1_ID));

        Map<Long, Collection<Integer>> check2 = ImmutableMap.<Long, Collection<Integer>>of(
                LIKED_COMMENT_1_ID, ImmutableSet.of(COMMENT_1_LIKER_1_ID, COMMENT_1_LIKER_2_ID, 333),
                LIKED_COMMENT_2_ID, ImmutableSet.of(222, 444, COMMENT_2_LIKER_2_ID, COMMENT_1_LIKER_1_ID));

        Map<Long, Collection<Integer>> retained2 = dao.retainExisting(check2);
        assertThat(retained2.size(), equalTo(2));
        assertThat(retained2, both(hasKey(LIKED_COMMENT_1_ID)).and(hasKey(LIKED_COMMENT_2_ID)));
        assertThat(retained2.get(LIKED_COMMENT_1_ID), containsInAnyOrder(COMMENT_1_LIKER_1_ID, COMMENT_1_LIKER_2_ID));
        assertThat(retained2.get(LIKED_COMMENT_2_ID), containsInAnyOrder(COMMENT_2_LIKER_2_ID));

        Map<Long, Collection<Integer>> check3 = ImmutableMap.<Long, Collection<Integer>>of(
                LIKED_COMMENT_1_ID, ImmutableSet.of(555),
                LIKED_COMMENT_2_ID, ImmutableSet.of(666, 777, 888));

        Map<Long, Collection<Integer>> retained3 = dao.retainExisting(check3);
        assertThat(retained3.size(), equalTo(0));
    }

    @Test
    public void testFindByCommentIdNoResults() {
        // there should be no likes for comment ID 10L
        assertThat(dao.findByCommentId(10L), emptyIterable());
    }

    @Test
    public void testFindByCommentId() {
        Iterable<Integer> likes = dao.findByCommentId(LIKED_COMMENT_2_ID);

        assertThat(likes, contains(COMMENT_2_LIKER_1_ID, COMMENT_2_LIKER_2_ID, COMMENT_2_LIKER_3_ID));
    }

    @Test
    public void testFindByCommentIdMaxResourcesExceeded() {
        when(commentLikeConfig.getMaxResources()).thenReturn(2);

        Iterable<Integer> likes = dao.findByCommentId(LIKED_COMMENT_2_ID);

        // should only return 2 likes
        assertThat(likes, contains(COMMENT_2_LIKER_1_ID, COMMENT_2_LIKER_2_ID));
    }

    @Test
    public void testFindByCommentIds() {
        Map<Long, Iterable<Integer>> likes = dao.findByCommentIds(
                ImmutableList.of(LIKED_COMMENT_1_ID, LIKED_COMMENT_2_ID, 100L));

        assertThat(likes, allOf(hasKey(LIKED_COMMENT_1_ID), hasKey(LIKED_COMMENT_2_ID), hasKey(100L)));

        assertThat(likes.get(LIKED_COMMENT_1_ID), contains(
                COMMENT_1_LIKER_1_ID,
                COMMENT_1_LIKER_2_ID
        ));
        assertThat(likes.get(LIKED_COMMENT_2_ID), contains(
                COMMENT_2_LIKER_1_ID,
                COMMENT_2_LIKER_2_ID,
                COMMENT_2_LIKER_3_ID
        ));
        assertThat(likes.get(100L), emptyIterable());
    }

    @Test
    public void testFindByCommentIdsMaxResourcesExceeded() {
        when(commentLikeConfig.getMaxResources()).thenReturn(4);

        Map<Long, Iterable<Integer>> likes = dao.findByCommentIds(
                ImmutableList.of(LIKED_COMMENT_1_ID, LIKED_COMMENT_2_ID, 100L));

        // should have all 3 keys but only 4 likes altogether
        assertThat(likes, allOf(hasKey(LIKED_COMMENT_1_ID), hasKey(LIKED_COMMENT_2_ID), hasKey(100L)));
        assertEquals(4, size(concat(likes.values())));
    }

    @Test
    public void testFindByCommentIdsManyCommentIds() {
        // this should cause 3 separate queries to get all the likes, but end result should be the same regardless
        Map<Long, Iterable<Integer>> likes = dao.findByCommentIds(
                ImmutableList.of(LIKED_COMMENT_1_ID, 100L, 101L, 102L, 103L, 104L, 105L, 106L, 107L, 108L, 109L, 110L));

        assertThat(likes, allOf(hasKey(LIKED_COMMENT_1_ID), hasKey(100L), hasKey(101L), hasKey(102L), hasKey(103L),
                hasKey(104L), hasKey(105L), hasKey(106L), hasKey(107L), hasKey(108L), hasKey(109L), hasKey(110L)));
        assertThat(likes.get(LIKED_COMMENT_1_ID), contains(COMMENT_1_LIKER_1_ID, COMMENT_1_LIKER_2_ID));
    }
}
