package com.atlassian.stash.internal.pull;

import com.atlassian.bitbucket.pull.PullRequestAction;
import com.atlassian.bitbucket.pull.PullRequestActivityPage;
import com.atlassian.bitbucket.pull.PullRequestActivityType;
import com.atlassian.bitbucket.pull.PullRequestState;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.stash.internal.Dao;
import com.atlassian.stash.internal.user.InternalApplicationUser;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * DAO for working with {@link InternalPullRequestActivity pull request activities}. This DAO manages all subclasses
 * of {@link InternalPullRequestActivity}.
 */
public interface PullRequestActivityDao extends Dao<Long, InternalPullRequestActivity> {

    /**
     * Counts the number of {@link InternalPullRequestActivity activities} associated with the given pull request
     * and user, alternatively filtering by the provided actions.
     *
     * @param pullRequestId the ID of the pull request in which to search for activities
     * @param userId the ID of the user to which the activity needs to be associated
     * @param includedActions the set of activity actions to include when counting.
     *                        If empty, the full {@link PullRequestAction#values() set} is assumed.
     *                        If an action is included in both the include and exclude sets, the exclude wins.
     * @param excludedActions the set of activity actions to use as an exclusion filter for the returned activities.
     *                        If an action is included in both the include and exclude sets, the exclude wins.
     * @return the number of activities associated with the pull request and user
     */
    long countByUser(long pullRequestId, long userId, @Nonnull Set<PullRequestAction> includedActions,
                     @Nonnull Set<PullRequestAction> excludedActions);

    /**
     * @param pullRequestIds a set containing 1 or more {@link InternalPullRequest#getGlobalId() global IDs}
     * @return a map from a pull request {@link InternalPullRequest#getGlobalId() global ID} to its comment count
     * @since 2.12
     */
    @Nonnull
    Map<Long, Long> countCommentsByPullRequest(@Nonnull Set<Long> pullRequestIds);

    /**
     * Deletes any activities which reference the specified comment. This is necessary to allow the comment to be
     * deleted due to foreign keys in the database.
     *
     * @param commentId the ID of the comment to delete activities for
     * @return the number of activities deleted
     * @since 2.11
     */
    int deleteByComment(long commentId);

    /**
     * Deletes activities for the specified {@link InternalPullRequest}.
     *
     * @param pullRequestId the {@link InternalPullRequest#getGlobalId() global ID} for the pull request
     * @return the number of activities deleted
     * @since 3.0.7, 3.1.4, 3.2.1
     */
    int deleteByPullRequest(long pullRequestId);

    /**
     * Deletes activities for pull requests {@link InternalPullRequest#getToRef() to} the specified repository.
     *
     * @param repositoryId the ID for the {@link InternalPullRequest#getScopeRepository() scoping repository}
     * @return the number of activities deleted
     * @since 3.0.7, 3.1.4, 3.2.1
     */
    int deleteByRepository(int repositoryId);

    /**
     * Finds and deletes all empty {@link InternalPullRequestRescopeActivity rescope activities}. A rescope activity
     * is considered empty if it adds no commits and removes no commits. Rescope activities which have not yet been
     * {@link InternalPullRequestRescopeActivity#isPrecalculated() calculated} are <i>not</i> considered empty.
     *
     * @return the number of activities deleted
     * @since 3.0
     * @since 2.12.1
     * @since 2.11.6
     * @since 2.10.4
     */
    int deleteEmptyRescopes();

    /**
     * Retrieves the {@link InternalPullRequestCommentActivity} associated with the comment whose ID is supplied.
     * This returns any {@link InternalPullRequestCommentActivity} and does no filtering unlike the find methods
     * in this class.
     *
     * @param pullRequestId the {@link InternalPullRequest#getGlobalId() globally-unique ID} for the pull request
     * @param commentId the ID of the comment
     * @return the activity associated with the comment or null if one was not found
     */
    @Nullable
    InternalPullRequestCommentActivity findByComment(long pullRequestId, long commentId);

    /**
     * Retrieves a page of {@link InternalPullRequestActivity activies} for the specified pull request. Activities of
     * any type are returned, ordered from most recent to least recent.
     *
     * @param pullRequestId the {@link InternalPullRequest#getGlobalId() globally-unique ID} for the pull request
     * @param pageRequest   start index and limit for the page of activities to return
     * @return a page containing zero or more activities for the specified pull request, most recent first
     */
    @Nonnull
    Page<InternalPullRequestActivity> findByPullRequest(long pullRequestId, @Nonnull PageRequest pageRequest);

    /**
     * Retrieves a list of {@link InternalPullRequestActivity#getId() IDs} for {@link InternalPullRequestRescopeActivity
     * rescope activities} for {@link PullRequestState#OPEN open} pull requests for which added
     * and removed commits have not been {@link InternalPullRequestRescopeActivity#isPrecalculated() calculated}.
     * <p>
     * This is intended to be used to prime the {@code ExecutorService} after a system restart, to allow it to continue
     * working its way through calculating rescope details on "new" activities. It is restricted to open pull requests
     * to reduce the likelihood of producing an excessively large queue on systems where thousands of pull requests have
     * already been processed. If a historical pull request is viewed, its rescopes will have their details calculated
     * and stored at that time.
     * <p>
     * The returned list may be empty, if commits for all rescopes on open pull requests have already been calculated,
     * but will never be {@code null}.
     *
     * @return a list of IDs for rescope activities for which commits still need to be calculated, which may be empty
     * @since 2.5
     */
    @Nonnull
    List<Long> findCalculableRescopes();

    /**
     * Searches for all pull request activities that have been created since the supplied {@link Date createdSince}
     * and that are of the supplied {@link PullRequestActivityType}.
     * Returns the latest activity of that type for each pull request.
     *
     * @param createdSince returned activities will have been created since this {@link Date}.
     * @param activityType the type of activity to find
     * @param pageRequest  describes the page of activities to retrieve
     * @return a page containing 0 or more pull request activities of the supplied type that have been created since the
     *         supplied {@link Date createdSince} with a maximum of one activity per pull request
     * @since 4.8
     */
    @Nonnull
    Page<InternalPullRequestActivity> findLatestCreatedSince(@Nonnull Date createdSince,
                                                             @Nonnull PullRequestActivityType activityType,
                                                             @Nonnull PageRequest pageRequest);

    /**
     * Retrieves a page of {@link InternalPullRequestActivity activities} containing the activity supplied.
     * If no such activity exists then an empty page is returned. It is only guaranteed that a returned
     * non-empty page will contain the supplied activity, not that this activity will be the first in the page.
     * <p>
     * The search for a page containing the activity is not exhaustive - after a certain fixed point
     * searching will cease and an empty page will be returned.
     * <p>
     * Activities of any type are returned, ordered from most recent to least recent.
     *
     * @param pullRequestId the id the pull request
     * @param activityId the id of the pull request's activity
     * @param pageRequest the page request, used to define the {@link PageRequest#getLimit() size} of the returned
     *                    page. Note that {@link PageRequest#getStart()} is <i>ignored</i> by this method.
     * @return a bidirectional page of activities containing the supplied activity - allowing consumers to both advance
     *         and reverse the paging
     */
    @Nonnull
    PullRequestActivityPage<InternalPullRequestActivity> findPageStartingAt(long pullRequestId, long activityId,
                                                                            @Nonnull PageRequest pageRequest);

    /**
     * Returns the set of {@link InternalApplicationUser users} who have, through their actions, generated any matching
     * {@link InternalPullRequestActivity activities} for the pull request identified by the supplied id.
     *
     * @param pullRequestId the id of the pull request
     * @param includedActions the set of activity actions to include when searching. If empty, the full
     *                        {@link PullRequestAction#values() set} is assumed.
     *                        If an action is included in both the include and exclude sets, the exclude wins.
     * @param excludedActions the set of activity actions to use as an exclusion filter for the returned activities.
     *                        If an action is included in both the include and exclude sets, the exclude wins.
     * @return the users who have made activities matching the includes and excludes for the pull request
     */
    @Nonnull
    Set<InternalApplicationUser> findUsersWithActivities(long pullRequestId,
                                                         @Nonnull Set<PullRequestAction> includedActions,
                                                         @Nonnull Set<PullRequestAction> excludedActions);

    /**
     * Retrieves a page of {@link InternalPullRequestActivity activities} matching the search criteria.
     *
     * @param searchCriteria criteria the activities should fulfill
     * @param pageRequest    page request delimiting the search
     * @return a page of {@link InternalPullRequestActivity activities}
     * @since 3.2
     */
    @Nonnull
    Page<InternalPullRequestActivity> search(PullRequestActivitySearchCriteria searchCriteria, PageRequest pageRequest);
}
