package com.atlassian.stash.internal.notification.web;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.comment.CommentAction;
import com.atlassian.bitbucket.mail.MailMessage;
import com.atlassian.bitbucket.mail.MailService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionValidationService;
import com.atlassian.bitbucket.pull.*;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.*;
import com.atlassian.stash.experimental.pull.ExperimentalPullRequestService;
import com.atlassian.stash.internal.notification.pull.activity.PullRequestActivityRenderer;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.time.DateUtils;

import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableSet;

public class NotificationRendererDebugServlet extends HttpServlet {

    private static final Pattern PR_PATTERN = Pattern.compile("/projects/([^/]+)/repos/([^/]+)/pull-requests/(\\d+)");

    private final AuthenticationContext authenticationContext;
    private final PermissionValidationService permissionValidationService;
    private final PullRequestActivityRenderer pullRequestActivityRenderer;
    private final RepositoryService repositoryService;
    private final ExperimentalPullRequestService pullRequestService;
    private final MailService mailService;

    public NotificationRendererDebugServlet(MailService mailService,
                                            PermissionValidationService permissionValidationService,
                                            PullRequestActivityRenderer pullRequestActivityRenderer,
                                            ExperimentalPullRequestService pullRequestService,
                                            RepositoryService repositoryService,
                                            AuthenticationContext authenticationContext) {
        this.authenticationContext = authenticationContext;
        this.permissionValidationService = permissionValidationService;
        this.pullRequestActivityRenderer = pullRequestActivityRenderer;
        this.repositoryService = repositoryService;
        this.pullRequestService = pullRequestService;
        this.mailService = mailService;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        permissionValidationService.validateForGlobal(Permission.ADMIN);
        resp.setContentType("text/html;charset=UTF-8");

        Matcher matcher = PR_PATTERN.matcher(req.getPathInfo());
        if (!matcher.matches()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        String projectKey = matcher.group(1);
        String repoKey = matcher.group(2);
        int prId = Integer.parseInt(matcher.group(3));

       Date sinceDate = parseDate(req.getParameter("since"));

        // All the activities!
        Repository repo = repositoryService.getBySlug(projectKey, repoKey);
        if (repo == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        PullRequest pullRequest = pullRequestService.getById(repo.getId(), prId);
        Iterable<PullRequestActivity> activities = getPullRequestActivities(repo, prId, sinceDate);

        Set<PullRequestActivity> nonCommentActivities = new HashSet<>();
        Set<Long> commentIds = new HashSet<>();
        extractActivityData(activities, nonCommentActivities, commentIds);
        Set<PullRequestCommentActivity> rootCommentActivities = getRootCommentActivities(repo.getId(), prId, commentIds);

        ApplicationUser user = authenticationContext.getCurrentUser();
        Iterable<MailMessage> messages = pullRequestActivityRenderer.render(user, pullRequest,
                Iterables.concat(nonCommentActivities, rootCommentActivities), commentIds,
                Collections.emptySet(), Collections.emptySet());

        for (MailMessage mailMessage : messages) {
            resp.getWriter().write(mailMessage.getText());
            if (req.getParameter("send") != null) {
                mailService.submit(mailMessage);
            }
        }

    }

    private Date parseDate(String dateString) {
        if (dateString != null) {
            try {
                return DateUtils.parseDate(dateString, "yyyy-MM-dd'T'HH:mm:ss");
            } catch (ParseException e) {
                // return the default
            }
        }
        return new Date(0);
    }

    private Iterable<PullRequestActivity> getPullRequestActivities(Repository repo, int prId, final Date sinceDate) {
        PullRequestActivitySearchRequest.Builder request = new PullRequestActivitySearchRequest.Builder(repo.getId(), prId);

        return pullRequestService.searchActivities(request.build(), PageUtils.newRequest(0, 99)).stream()
                .filter(activity -> activity.getCreatedDate().after(sinceDate))
                .collect(toImmutableSet());
    }

    private void extractActivityData(Iterable<PullRequestActivity> activities, final Set<PullRequestActivity> nonCommentActivities, final Set<Long> commentIds) {
        for (PullRequestActivity activity : activities) {
            activity.accept(new PullRequestActivityVisitor() {
                @Override
                public void visit(@Nonnull PullRequestActivity activity) {
                    PullRequestAction action = activity.getAction();
                    if (action == PullRequestAction.OPENED || action == PullRequestAction.UPDATED) {
                        return;
                    }
                    nonCommentActivities.add(activity);
                }

                @Override
                public void visit(@Nonnull PullRequestMergeActivity activity) {
                    nonCommentActivities.add(activity);
                }

                @Override
                public void visit(@Nonnull PullRequestRescopeActivity activity) {
                    nonCommentActivities.add(activity);
                }

                @Override
                public void visit(@Nonnull PullRequestCommentActivity activity) {
                    commentIds.add(activity.getComment().getId());
                }

                @Override
                public void visit(@Nonnull PullRequestReviewersUpdatedActivity activity) {
                    nonCommentActivities.add(activity);
                }
            });
        }
    }

    private Set<PullRequestCommentActivity> getRootCommentActivities(int repoId, int prId, Set<Long> commentIds) {
        Set<Long> rootCommentIds = new HashSet<>();
        for (Long commentId : commentIds) {
            Long rootCommentId = pullRequestService.getRootCommentId(repoId, prId, commentId);
            rootCommentIds.add(rootCommentId);
        }
        if (rootCommentIds.isEmpty()) {
            return Collections.emptySet();
        }

        final PullRequestActivitySearchRequest searchRequest = new PullRequestActivitySearchRequest.Builder(repoId, prId)
                .types(PullRequestActivityType.COMMENT)
                .commentActions(CommentAction.ADDED)
                .commentIds(rootCommentIds)
                .build();
        PagedIterable<PullRequestActivity> pagedIterable = new PagedIterable<>(request ->
                pullRequestService.searchActivities(searchRequest, request), PageRequest.MAX_PAGE_LIMIT);

        final Set<PullRequestCommentActivity> result = new HashSet<>();
        for (PullRequestActivity activity : pagedIterable) {
            activity.accept(new AbstractPullRequestActivityVisitor() {
                @Override
                public void visit(@Nonnull PullRequestCommentActivity activity) {
                    result.add(activity);
                }
            });
        }
        return result;
    }

}
