package com.atlassian.stash.internal.task;

import com.atlassian.bitbucket.AbstractServiceTest;
import com.atlassian.bitbucket.comment.NoSuchCommentException;
import com.atlassian.bitbucket.event.TestEventPublisher;
import com.atlassian.bitbucket.event.task.TaskCreatedEvent;
import com.atlassian.bitbucket.event.task.TaskDeletedEvent;
import com.atlassian.bitbucket.event.task.TaskUpdatedEvent;
import com.atlassian.bitbucket.task.*;
import com.atlassian.bitbucket.task.TaskAnchorType;
import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.bitbucket.NoSuchEntityException;
import com.atlassian.bitbucket.hamcrest.PageMatchers;
import com.atlassian.bitbucket.hamcrest.PageRequestMatcher;
import com.atlassian.bitbucket.hamcrest.SimpleTypeSafeMatcher;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.comment.CommentDao;
import com.atlassian.stash.internal.comment.InternalComment;
import com.atlassian.stash.internal.project.InternalNormalProject;
import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.stash.internal.pull.*;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.task.enrich.TaskEnricher;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.stash.internal.user.InternalNormalUser;
import com.atlassian.bitbucket.pull.PullRequestRole;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import org.hamcrest.Matcher;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.atlassian.bitbucket.mockito.PageAnswer.withLastPageOf;
import static com.atlassian.bitbucket.task.TaskAnchorType.COMMENT;
import static com.atlassian.bitbucket.permission.Permission.REPO_READ;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.contains;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.anyListOf;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.notNull;

@RunWith(MockitoJUnitRunner.class)
public class DefaultTaskServiceTest extends AbstractServiceTest {

    private static final long COMMENT_ID = 99L;
    private static final InternalComment ANCHOR = new InternalComment.Builder().id(COMMENT_ID).build();
    private static final String CURRENT_USER = "user";
    private static final int MAX_ANCHORS_PER_REQUEST = 3;
    private static final int MAX_CONTEXTS_PER_REQUEST = 3;
    private static final int MAX_TASKS_PER_REQUEST = 3;
    public static final Answer<InternalTask> TASK_ECHO_ANSWER = new Answer<InternalTask>() {
        @Override
        public InternalTask answer(InvocationOnMock invocation) throws Throwable {
            return (InternalTask) invocation.getArguments()[0];
        }
    };
    private static final InternalProject PROJECT = new InternalNormalProject.Builder().id(666).key("project").build();
    private static final InternalRepository REPOSITORY = new InternalRepository.Builder().id(999)
            .name("repository")
            .project(PROJECT)
            .build();
    private static final InternalPullRequest PULL_REQUEST = new InternalPullRequest.Builder().scopedId(999L)
            .toRef(new InternalPullRequestRef.Builder()
                    .repository(REPOSITORY)
                    .build())
            .build();

    static {
        PULL_REQUEST.addParticipant(new InternalPullRequestParticipant.Builder(PULL_REQUEST)
                .role(PullRequestRole.AUTHOR)
                .user(new InternalNormalUser.Builder()
                        .slug("task-creator")
                        .build())
                .build());
    }

    @Mock
    private CommentDao commentDao;
    @Spy
    private I18nService i18nService = new SimpleI18nService();
    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private InternalPullRequestParticipantHelper participantHelper;
    @Mock
    private PermissionService permissionService;
    @Mock
    private PullRequestDao pullRequestDao;
    @Mock
    private TaskDao taskDao;
    @Mock
    private TaskEnricher taskEnricher;
    @Mock
    private TaskPermissionsHelper taskPermissionsHelper;
    @Mock
    private TaskPostProcessor taskPostProcessor;
    @Spy
    private TestEventPublisher eventPublisher = new TestEventPublisher();

    private DefaultTaskService service;

    public DefaultTaskServiceTest() {
        super(DefaultTaskService.class, TaskService.class);
    }

    @Before
    public void setUp() throws Exception {
        InternalApplicationUser currentUser = newUser(CURRENT_USER);
        when(authenticationContext.getCurrentUser()).thenReturn(currentUser);

        service = new DefaultTaskService(authenticationContext, commentDao, eventPublisher, i18nService,
                MAX_ANCHORS_PER_REQUEST, MAX_CONTEXTS_PER_REQUEST, MAX_TASKS_PER_REQUEST, participantHelper,
                permissionService, pullRequestDao, taskDao, taskEnricher, taskPermissionsHelper, taskPostProcessor);
    }

    @Test
    public void testCount() throws Exception {
        InternalTaskSearchRequest request = new InternalTaskSearchRequest.Builder(PULL_REQUEST)
                .build();
        TaskCount taskCount = mock(TaskCount.class);
        when(taskDao.count(eq(request))).thenReturn(taskCount);

        assertEquals(taskCount, service.count(request));
    }

    @Test
    public void testCountWithTooManyAnchors() throws Exception {
        List<Long> anchorIds = new ArrayList<>();
        for (int i = 0; i < MAX_ANCHORS_PER_REQUEST * 2; i++) {
            anchorIds.add(newAnchor(i).getId());
        }
        InternalTaskSearchRequest request = new InternalTaskSearchRequest.Builder(PULL_REQUEST)
                .anchors(anchorIds, TaskAnchorType.COMMENT)
                .build();
        TaskCount taskCount = mock(TaskCount.class);
        when(taskDao.count(notNull(InternalTaskSearchRequest.class))).thenReturn(taskCount);

        assertEquals(taskCount, service.count(request));

        ArgumentCaptor<InternalTaskSearchRequest> actualRequest = ArgumentCaptor.forClass(InternalTaskSearchRequest.class);
        verify(taskDao).count(actualRequest.capture());
        assertThat(actualRequest.getValue().getAnchorIds(), hasSize(MAX_ANCHORS_PER_REQUEST));
    }

    @Test
    public void testCountForContextsWithTooMany() throws Exception {
        List<InternalTaskContext> contexts = new ArrayList<>();
        for (int i = 0; i < MAX_CONTEXTS_PER_REQUEST * 2; i++) {
            contexts.add(newContext());
        }
        Map<InternalTaskContext, TaskCount> counts = Collections.singletonMap(newContext(), mock(TaskCount.class));
        when(taskDao.countForContexts(anyListOf(InternalTaskContext.class))).thenReturn(counts);

        assertEquals(counts, service.countForContexts(contexts));

        ArgumentCaptor<Iterable> actualContexts = ArgumentCaptor.forClass(Iterable.class);
        verify(taskDao).countForContexts(actualContexts.capture());
        assertThat(actualContexts.getValue(), (Matcher) iterableWithSize(MAX_ANCHORS_PER_REQUEST));
    }

    @Test
    public void testCreate() throws Exception {
        String text = "text";
        TaskCreateRequest request = new TaskCreateRequest.Builder()
                .anchorType(COMMENT)
                .anchorId(COMMENT_ID)
                .text(text)
                .build();
        InternalTask task = new InternalTask.Builder().anchor(ANCHOR).context(PULL_REQUEST).build();
        doReturn(task).when(taskDao).create(argThat(task(ANCHOR, PULL_REQUEST, CURRENT_USER, TaskState.OPEN, text,
                new Date(), TimeUnit.SECONDS.toMillis(1))));
        when(taskPostProcessor.process(eq(task))).thenReturn(task);
        when(pullRequestDao.findByComment(COMMENT_ID)).thenReturn(PULL_REQUEST);
        when(commentDao.getById(COMMENT_ID)).thenReturn(ANCHOR);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);

        assertEquals(task, service.create(request));
        assertThat(eventPublisher.getPublishedEvents(), contains(allOf(
                instanceOf(TaskCreatedEvent.class), hasProperty("task", equalTo(task)))));
        verify(participantHelper).makeCurrentUserParticipantAndWatcher(PULL_REQUEST);
    }

    @Test(expected = NoSuchCommentException.class)
    public void testCreateForUnknownComment() throws Exception {
        TaskCreateRequest request = new TaskCreateRequest.Builder()
                .anchorId(COMMENT_ID)
                .anchorType(COMMENT)
                .text("text")
                .build();
        when(pullRequestDao.findByComment(COMMENT_ID)).thenReturn(null);

        service.create(request);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testCannotCreateTaskWithBlankText() throws Exception {
        new TaskCreateRequest.Builder()
                .anchorId(COMMENT_ID)
                .anchorType(COMMENT)
                .text("  ")
                .build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testCannotCreateTaskWithEmptyText() throws Exception {
        new TaskCreateRequest.Builder()
                .anchorId(COMMENT_ID)
                .anchorType(COMMENT)
                .text("")
                .build();
    }

    @Test(expected = NoSuchEntityException.class)
    public void testCannotCreateTaskWhenContextIsNotVisible() throws Exception {
        String text = "text";
        TaskCreateRequest request = new TaskCreateRequest.Builder()
                .anchorType(COMMENT)
                .anchorId(COMMENT_ID)
                .text(text)
                .build();
        when(pullRequestDao.findByComment(COMMENT_ID)).thenReturn(PULL_REQUEST);
        when(commentDao.getById(COMMENT_ID)).thenReturn(ANCHOR);

        service.create(request);
    }

    @Test
    public void testCreateTaskShouldNormalizeWhitespace() throws Exception {
        String wikiText = " this is\n\n```\ntext with markdown\n```\n";
        String normalizedText = "this is ``` text with markdown ```";
        TaskCreateRequest request = new TaskCreateRequest.Builder()
                .anchorId(COMMENT_ID)
                .anchorType(COMMENT)
                .text(wikiText)
                .build();
        when(taskDao.create(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(pullRequestDao.findByComment(COMMENT_ID)).thenReturn(PULL_REQUEST);
        when(commentDao.getById(COMMENT_ID)).thenReturn(ANCHOR);
        when(taskPostProcessor.process(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);
        assertEquals(normalizedText, service.create(request).getText());
    }

    @Test
    public void testDelete() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).build();

        when(taskDao.getById(42L)).thenReturn(task);
        when(taskPermissionsHelper.hasFullPermission(eq(PULL_REQUEST), eq(task))).thenReturn(true);
        when(taskPostProcessor.process(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);

        service.delete(42L);

        verify(taskDao).delete(eq(task));
        assertThat(eventPublisher.getPublishedEvents(), contains(allOf(
                instanceOf(TaskDeletedEvent.class), hasProperty("task", equalTo(task)))));
    }

    @Test
    public void testDeleteWhenUserIsAdmin() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).build();

        when(taskDao.getById(42L)).thenReturn(task);
        when(taskPermissionsHelper.isRepoAdmin(eq(REPOSITORY))).thenReturn(true);
        when(taskPostProcessor.process(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);

        service.delete(42L);

        verify(taskDao).delete(eq(task));
        assertThat(eventPublisher.getPublishedEvents(), contains(allOf(
                instanceOf(TaskDeletedEvent.class), hasProperty("task", equalTo(task)))));
    }

    @Test(expected = NoSuchTaskException.class)
    public void testDeleteWhenUserWithoutPermission() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).state(TaskState.RESOLVED).build();

        when(taskDao.getById(42L)).thenReturn(task);

        service.delete(42L);
    }

    @Test(expected = IllegalTaskStateException.class)
    public void testDeleteWhenTaskAlreadyResolved() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).state(TaskState.RESOLVED).build();

        when(taskDao.getById(42L)).thenReturn(task);
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), eq(task))).thenReturn(true);
        when(taskPostProcessor.process(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);

        service.delete(42L);
    }

    @Test
    public void testGetById() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).build();
        when(taskDao.getById(eq(42L))).thenReturn(task);
        when(taskPostProcessor.process(eq(task))).thenReturn(task);
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);

        assertEquals(task, service.getById(42L));

        verify(taskPostProcessor).process(eq(task));
    }

    @Test
    public void testGetByIdWhenContextIsNotVisible() throws Exception {
        InternalTask task = new InternalTask.Builder().context(PULL_REQUEST).build();
        when(taskDao.getById(eq(42L))).thenReturn(task);
        when(taskPostProcessor.process(eq(task))).thenReturn(task);

        assertNull(service.getById(42L));
    }

    @Test
    public void testGetByIdWhenTaskDoesNotExist() throws Exception {
        assertNull(service.getById(42L));
    }

    @Test
    public void testHasTasks() throws Exception {
        TaskAnchor anchor = newAnchor(1);
        assertFalse(service.hasTasks(anchor));
        when(taskDao.hasTasks(eq(anchor))).thenReturn(true);
        assertTrue(service.hasTasks(anchor));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSearch() throws Exception {
        InternalTaskSearchRequest searchRequest = new InternalTaskSearchRequest.Builder(PULL_REQUEST)
                .anchors(asList(ANCHOR.getId()), TaskAnchorType.COMMENT)
                .build();
        PageRequest pageRequest = PageUtils.newRequest(0, MAX_TASKS_PER_REQUEST - 1);
        InternalTask task = new InternalTask.Builder().anchor(ANCHOR).context(PULL_REQUEST).build();
        when(taskDao.search(eq(searchRequest), eq(pageRequest))).thenAnswer(withLastPageOf(task));

        assertThat(service.search(searchRequest, pageRequest, true), PageMatchers.<Task>isPageOf(task));
        verify(taskPostProcessor).processAll(eq(PULL_REQUEST), (Iterable<InternalTask>) argThat(IsIterableContainingInOrder.contains(task)));
        verify(taskEnricher).enrich(eq(PULL_REQUEST), (Iterable<InternalTask>) argThat(IsIterableContainingInOrder.contains(task)));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSearchWithTooLargeLimit() throws Exception {
        InternalTaskSearchRequest searchRequest = new InternalTaskSearchRequest.Builder(PULL_REQUEST)
                .anchors(asList(ANCHOR.getId()), TaskAnchorType.COMMENT)
                .build();
        PageRequest pageRequest = PageUtils.newRequest(0, MAX_TASKS_PER_REQUEST * 2);
        InternalTask task = new InternalTask.Builder().anchor(ANCHOR).context(PULL_REQUEST).build();
        when(taskDao.search(eq(searchRequest), argThat(PageRequestMatcher.pageRequest(0, MAX_TASKS_PER_REQUEST)))).thenAnswer(withLastPageOf(task));

        assertThat(service.search(searchRequest, pageRequest, false), PageMatchers.<Task>isPageOf(task));
        verify(taskPostProcessor).processAll(eq(PULL_REQUEST), (Iterable<InternalTask>) argThat(IsIterableContainingInOrder.contains(task)));
        verify(taskEnricher, never()).enrich(eq(PULL_REQUEST), (Iterable<InternalTask>) argThat(IsIterableContainingInOrder.contains(task)));
    }

    @Test
    public void testUpdate() throws Exception {
        String taskCreator = "task creator";
        Date taskCreatedDate = newDate(2014, 1, 1);
        InternalTask originalTask = new InternalTask.Builder()
                .id(42L)
                .author(newUser(taskCreator))
                .createdDate(taskCreatedDate)
                .state(TaskState.OPEN)
                .text("text")
                .anchor(ANCHOR)
                .context(PULL_REQUEST)
                .build();

        TaskState newState = TaskState.RESOLVED;
        String newText = "new text";
        InternalTask updatedTask = new InternalTask.Builder(originalTask).state(newState).text(newText).build();
        doReturn(updatedTask).when(taskDao).update(
                argThat(task(originalTask.getAnchor(), originalTask.getContext(),
                        taskCreator, newState, newText,
                        taskCreatedDate, 0)));
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), eq(originalTask))).thenReturn(true);
        when(taskPostProcessor.process(eq(updatedTask))).thenReturn(updatedTask);

        TaskUpdateRequest request = new TaskUpdateRequest.Builder(originalTask).state(newState).text(newText).build();
        assertEquals(updatedTask, service.update(request));
        assertThat(eventPublisher.getPublishedEvents(), contains(allOf(
                instanceOf(TaskUpdatedEvent.class),
                hasProperty("task", equalTo(updatedTask)),
                hasProperty("previousState", equalTo(TaskState.OPEN)),
                hasProperty("previousText", equalTo("text")))));
        verify(participantHelper).makeCurrentUserParticipantAndWatcher(PULL_REQUEST);
    }

    @Test
    public void testUpdateTaskShouldNormalizeWhitespace() throws Exception {
        String wikiText = " this is\n\n```\ntext with markdown\n```\n";
        String normalizedText = "this is ``` text with markdown ```";
        InternalTask originalTask = new InternalTask.Builder()
                .id(42L)
                .author(newUser("task creator"))
                .createdDate(newDate(2014, 1, 1))
                .state(TaskState.OPEN)
                .text("text")
                .anchor(ANCHOR)
                .context(PULL_REQUEST)
                .build();
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), eq(originalTask))).thenReturn(true);
        when(taskDao.update(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);
        when(taskPostProcessor.process(any(InternalTask.class))).thenAnswer(TASK_ECHO_ANSWER);

        TaskUpdateRequest request = new TaskUpdateRequest.Builder(originalTask).text(wikiText).build();
        Task updatedTask = service.update(request);
        assertEquals(normalizedText, updatedTask.getText());
        assertThat(eventPublisher.getPublishedEvents(), contains(allOf(
                instanceOf(TaskUpdatedEvent.class),
                hasProperty("task", equalTo(updatedTask)),
                hasProperty("previousState", equalTo(TaskState.OPEN)),
                hasProperty("previousText", equalTo("text")))));
    }

    @Test(expected = NoSuchEntityException.class)
    public void testUpdateWhenContextIsNotVisible() throws Exception {
        String taskCreator = "task creator";
        Date taskCreatedDate = newDate(2014, 1, 1);
        InternalTask originalTask = new InternalTask.Builder()
                .id(42L)
                .author(newUser(taskCreator))
                .createdDate(taskCreatedDate)
                .state(TaskState.RESOLVED)
                .text("text")
                .anchor(ANCHOR)
                .context(PULL_REQUEST)
                .build();
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), eq(originalTask))).thenReturn(true);

        TaskState newState = TaskState.OPEN;
        String newText = "new text";
        TaskUpdateRequest request = new TaskUpdateRequest.Builder(originalTask).state(newState).text(newText).build();
        service.update(request);
    }

    @Test(expected = AuthorisationException.class)
    public void testUpdateWhenUserHasNoFullAccess() throws Exception {
        String taskCreator = "task creator";
        Date taskCreatedDate = newDate(2014, 1, 1);
        InternalTask originalTask = new InternalTask.Builder()
                .id(42L)
                .author(newUser(taskCreator))
                .createdDate(taskCreatedDate)
                .state(TaskState.OPEN)
                .text("text")
                .anchor(ANCHOR)
                .context(PULL_REQUEST)
                .build();
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), notNull(InternalTask.class))).thenReturn(false);

        TaskState newState = TaskState.RESOLVED;
        String newText = "new text";
        TaskUpdateRequest request = new TaskUpdateRequest.Builder(originalTask).state(newState).text(newText).build();
        service.update(request);
    }

    @Test(expected = IllegalTaskStateException.class)
    public void testUpdateWhenTaskIsResolved() throws Exception {
        String taskCreator = "task creator";
        Date taskCreatedDate = newDate(2014, 1, 1);
        InternalTask originalTask = new InternalTask.Builder()
                .id(42L)
                .author(newUser(taskCreator))
                .createdDate(taskCreatedDate)
                .state(TaskState.RESOLVED)
                .text("text")
                .anchor(ANCHOR)
                .context(PULL_REQUEST)
                .build();
        when(permissionService.hasRepositoryPermission(REPOSITORY, REPO_READ)).thenReturn(true);
        when(taskPermissionsHelper.hasFullPermission(notNull(InternalTaskContext.class), eq(originalTask))).thenReturn(true);

        TaskState newState = TaskState.OPEN;
        String newText = "new text";
        TaskUpdateRequest request = new TaskUpdateRequest.Builder(originalTask).state(newState).text(newText).build();
        service.update(request);
    }

    private TaskAnchor newAnchor(long id) {
        TaskAnchor mock = mock(TaskAnchor.class);
        when(mock.getId()).thenReturn(id);
        return mock;
    }

    private InternalTaskContext newContext() {
        return mock(InternalTaskContext.class);
    }

    private static Date newDate(int year, int month, int day) {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(year, month, day);
        return calendar.getTime();
    }

    private static InternalApplicationUser newUser(String name) {
        return new InternalNormalUser.Builder().name(name).build();
    }

    private static Matcher<InternalTask> task(final TaskAnchor anchor, final InternalTaskContext context,
            final String author, final TaskState state, final String text,
            final Date createDate, final long deltaInMillis) {
        return new SimpleTypeSafeMatcher<InternalTask>() {
            @Override
            protected boolean matchesSafely(InternalTask task) {
                return task.getAnchor().equals(anchor)
                        && task.getAuthor().getUsername().equals(author)
                        && task.getContext().equals(context)
                        && (Math.abs(createDate.getTime() - task.getCreatedDate().getTime()) <= deltaInMillis)
                        && task.getState().equals(state)
                        && task.getText().equals(text);
            }
        };
    }

}