package com.atlassian.stash.internal.comment;

import com.atlassian.bitbucket.comment.CommentChain;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.task.Task;
import com.atlassian.bitbucket.task.TaskAnchor;
import com.atlassian.bitbucket.task.TaskAnchorType;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.stash.internal.pull.PullRequestDao;
import com.atlassian.stash.internal.pull.comment.CommentPostProcessor;
import com.atlassian.stash.internal.task.InternalTaskContext;
import com.atlassian.stash.internal.task.InternalTaskSearchRequest;
import com.atlassian.stash.internal.task.InternalTaskService;

import java.util.*;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableSet;
import static com.atlassian.bitbucket.util.MoreStreams.streamIterable;
import static com.atlassian.stash.internal.InternalConverter.convertToInternalPullRequest;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

/**
 * {@link CommentPostProcessor Post-processor} fetching the tasks of one or more comments.
 *
 * @since 3.3
 */
public class TaskCommentPostProcessor implements CommentPostProcessor {

    private final int maxTasksPerSearch;
    private final PullRequestDao pullRequestDao;
    private final InternalTaskService taskService;

    public TaskCommentPostProcessor(PullRequestDao pullRequestDao, InternalTaskService taskService, int maxTasksPerSearch) {
        this.maxTasksPerSearch = maxTasksPerSearch;
        this.pullRequestDao = pullRequestDao;
        this.taskService = taskService;
    }

    @Override
    public InternalComment process(InternalCommentable entity, InternalComment comment) {
        processAll(entity, Collections.singleton(comment));
        return comment;
    }

    @Override
    public Set<InternalComment> processAll(InternalCommentable entity, Set<InternalComment> rootComments) {
        if (rootComments.isEmpty()) {
            return rootComments;
        }

        InternalTaskContext taskContext;
        if (entity instanceof InternalTaskContext) {
            taskContext = convertToInternalPullRequest((PullRequest) entity);
        } else {
            // ASSUMPTION: All comments being enriched with the same entity were created on the same view of that entity
            // E.g. all the root comments were either made on the PR or on the standalone commit, but not both.
            long commentId = rootComments.iterator().next().getId();
            taskContext = pullRequestDao.findByComment(commentId);
            if (taskContext == null || entity.getScopeRepository().getId() != taskContext.getScopeRepository().getId()) {
                return rootComments;
            }
        }

        return processAll(taskContext, rootComments);
    }

    private Set<InternalComment> processAll(InternalTaskContext taskContext, Set<InternalComment> rootComments) {
        Iterable<InternalComment> allComments = new CommentChain<>(rootComments);

        Map<Long, List<Task>> tasksByComment = retrieveTasks(taskContext, allComments);
        for (InternalComment comment : allComments) {
            if (!comment.hasTasks()) {
                List<Task> tasksForComment = tasksByComment.get(comment.getId());

                comment.setTasks(tasksForComment == null ? emptyList() : unmodifiableList(tasksForComment));
            }
        }

        return rootComments;
    }

    private Map<Long, List<Task>> retrieveTasks(InternalTaskContext pullRequest, Iterable<? extends TaskAnchor> anchors) {
        Set<Long> allCommentIds = streamIterable(anchors)
                .map(TaskAnchor::getId)
                .collect(toImmutableSet());
        InternalTaskSearchRequest taskSearchRequest = new InternalTaskSearchRequest.Builder(pullRequest)
                .anchors(allCommentIds, TaskAnchorType.COMMENT)
                .build();

        Page<Task> page = taskService.search(taskSearchRequest, PageUtils.newRequest(0, maxTasksPerSearch), false);

        // index the tasks per anchor, keeping the original order of the tasks in the search results
        Map<Long, List<Task>> tasks = new HashMap<>();
        for (Task task : page.getValues()) {
            TaskAnchor anchor = task.getAnchor();

            List<Task> tasksForAnchor = tasks.get(anchor.getId());
            if (tasksForAnchor == null) {
                tasks.put(anchor.getId(), tasksForAnchor = new ArrayList<>(3));
            }
            tasksForAnchor.add(task);
        }

        return tasks;
    }
}
