package com.atlassian.stash.internal.comment;

import com.atlassian.fugue.Iterables;
import com.atlassian.bitbucket.annotation.Dao;
import com.atlassian.bitbucket.annotation.DataSet;
import com.atlassian.bitbucket.comment.Comment;
import com.atlassian.stash.internal.AbstractDaoTest;
import com.atlassian.stash.internal.DataSets;
import com.atlassian.stash.internal.user.HibernateStashUserDao;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.stash.internal.user.StashUserDao;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.google.common.collect.ImmutableList;
import liquibase.util.StringUtils;
import org.junit.Test;

import java.util.Date;

@DataSet({DataSets.USERS, DataSets.COMMENTS})
public class HibernateCommentDaoTest extends AbstractDaoTest {

    @Dao
    private HibernateCommentDao dao;
    @Dao(HibernateStashUserDao.class)
    private StashUserDao userDao;

    @Test
    public void testCreate() {
        InternalApplicationUser author = userDao.loadById(1);

        String text = "Needs more cowbell: " + StringUtils.repeat(DESCRIPTION_CHARS, 100);
        InternalComment comment = new InternalComment.Builder()
                .author(author)
                .createdDate(new Date())
                .text(text)
                .updatedDate(new Date())
                .build();
        InternalComment created = dao.create(comment);
        assertNotNull(created.getId());
        assertEquals(created.getId(), comment.getId());

        flush(); //The session _must_ be flushed before evict or the insert _may_ never happen
        evict(created, created.getId());

        InternalComment loaded = dao.getById(created.getId());
        assertNotNull(loaded);
        assertEquals("Description did not round-trip correctly", text, loaded.getText());
    }

    @Test
    public void testCreateTopLevelReply() {
        InternalApplicationUser user = userDao.loadById(3);
        InternalComment parent = dao.loadById(1L);

        InternalComment reply = new InternalComment.Builder()
                .author(user)
                .createdDate(new Date())
                .parent(parent)
                .text("Tests look good. Thanks")
                .updatedDate(new Date())
                .build();
        assertNotNull("Root comment should have been set automatically by the builder", reply.getRoot());

        InternalComment createdReply = dao.create(reply);
        assertNotNull(createdReply.getId());
        assertEquals(createdReply.getId(), reply.getId());

        flush();

        //Verify pointer chasing
        assertTrue(parent.getComments().contains(createdReply));
        assertTrue(createdReply.getParent().getComments().contains(createdReply));
    }

    @Test
    public void testCreateSecondLevelReply() {
        InternalApplicationUser user = userDao.loadById(3);
        InternalComment parent = dao.loadById(3L);

        InternalComment replyReply = new InternalComment.Builder()
                .author(user)
                .createdDate(new Date())
                .parent(parent)
                .text("Tests look good. Thanks")
                .updatedDate(new Date())
                .build();
        assertNotNull("Root comment should have been set automatically by the builder", replyReply.getRoot());

        InternalComment createdReplyReply = dao.create(replyReply);
        assertNotNull(createdReplyReply.getId());
        assertEquals(createdReplyReply.getId(), replyReply.getId());

        flush();

        //Verify pointer chasing
        assertTrue(parent.getComments().contains(createdReplyReply));
        assertTrue(createdReplyReply.getParent().getComments().contains(createdReplyReply));
    }

    @Test
    public void testDelete() {
        InternalComment comment = dao.loadById(3L);
        assertNotNull(comment);
        dao.delete(comment);

        flush();

        assertNull(dao.getById(3L));
    }

    @Test
    public void testDeleteLeafReply() {
        InternalComment comment = dao.loadById(3L);
        assertNotNull(comment);
        dao.delete(comment);

        flush();

        //Verify pointer chasing
        InternalComment parent = dao.getById(2L);
        assertFalse(comment.getParent().getComments().contains(comment));
        assertFalse(parent.getComments().contains(comment));
    }

    @Test
    public void testFindAll() {
        Page<InternalComment> page = dao.findAll(new PageRequestImpl(0, 7));
        assertNotNull(page);
        assertFalse(page.getIsLastPage());
        assertEquals(7, page.getSize());
        assertCommentIds(page.getValues(), 1L, 2L, 3L, 4L, 5L, 6L, 7L);

        page = dao.findAll(page.getNextPageRequest());
        assertNotNull(page);
        assertTrue(page.getIsLastPage());
        assertEquals(5, page.getSize());
        assertCommentIds(page.getValues(), 8L, 9L, 10L, 11L, 12L);
    }

    @Test
    public void testUpdate() {
        Date timestamp = new Date();

        InternalComment initial = dao.getById(3L);
        int version = initial.getVersion();

        InternalComment modified = new InternalComment.Builder(initial)
                .text("Unit tests have been added, and the pull request has been updated.")
                .updatedDate(timestamp)
                .build();
        dao.update(modified);

        //Flushing the session causes the version number to increment, but should not commit the transaction
        flush();

        InternalComment updated = dao.getById(3L);
        assertEquals("Unit tests have been added, and the pull request has been updated.", updated.getText());
        assertEquals(timestamp, updated.getUpdatedDate());
        assertNotEquals("Entity version was not updated", version, updated.getVersion());
    }

    private static void assertCommentIds(Iterable<? extends Comment> comments, long... expectedIds) {
        if (expectedIds.length == 0) {
            assertTrue(Iterables.isEmpty().apply(comments));
        } else {
            ImmutableList<Comment> list = ImmutableList.copyOf(comments);
            assertEquals("Found [" + list.size() + "] values where [" + expectedIds.length + "] were expected",
                    expectedIds.length, list.size());

            for (int i = 0; i < expectedIds.length; ++i) {
                assertEquals("Value [" + (i + 1) + "] did not have the expected ID",
                        expectedIds[i], list.get(i).getId());
            }
        }
    }
}
