package com.atlassian.bitbucket.mesh.git.pull;

import com.atlassian.bitbucket.mesh.git.content.ChangeCallback;
import com.atlassian.bitbucket.mesh.git.exception.NoMergeBaseException;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcRepositoryIntegrityIssue;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * A manager for performing pull request processing.
 */
public interface PullRequestManager {

    /**
     * Checks if the pull requests in a given repository are up to date with what Bitbucket knows
     *
     * @param repository                    repository whose integrity is to be checked
     * @param latestPullRequestId           latest pull request ID in the repository that Bitbucket knows about
     * @return {@link RpcRepositoryIntegrityIssue issue} that was found in this repository. {@link Optional#empty()}
     *         if none exists
     */
    @Nonnull
    Optional<RpcRepositoryIntegrityIssue> checkIntegrity(@Nonnull Repository repository, long latestPullRequestId);

    /**
     * Deletes the {@link PullRequest pull request}.
     *
     * @param pullRequest the pull request to delete
     * @return the refs that were updated when the pull request was deleted
     */
    @Nonnull
    List<RefUpdate> delete(@Nonnull PullRequest pullRequest);

    /**
     * Finds the {@code git merge-base} between the {@link PullRequest#getFromRef() from} and
     * {@link PullRequest#getToRef() to} refs on the pull request and returns its hash. If the {@code from} and
     * {@code to} refs are not related and {@code allowUnrelated} is {@code true}, the null tree is returned as
     * the merge base.
     *
     * @param pullRequest    the pull request to find the merge base for
     * @param allowUnrelated {@code true} if unrelated refs should yield the null tree; otherwise, {@code false}
     *                       to throw {@link NoMergeBaseException}
     * @return the merge base between the pull request's from and to refs
     * @throws NoMergeBaseException if {@code allowUnrelated} is {@code false} and the pull request's from and to refs
     *                              do not share a common ancestor
     */
    @Nonnull
    String findMergeBase(@Nonnull PullRequest pullRequest, boolean allowUnrelated);

    /**
     * Resolves the {@link PullRequestEffectiveDiff effective diff} for the specified {@link PullRequest pull request}.
     *
     * @param parameters parameters for the pull request to resolve the effective diff for
     * @return the effective diff for the specified pull request
     * @throws NoMergeBaseException if {@link GetPullRequestEffectiveDiffParameters#isAllowUnrelated} is {@code false}
     *                              and the pull request's from and to refs do not share a common ancestor
     */
    @Nonnull
    PullRequestEffectiveDiff getEffectiveDiff(@Nonnull GetPullRequestEffectiveDiffParameters parameters);

    /**
     * Analyzes the provided {@link PullRequestRescope rescopes} and determines for each whether the scope change
     * results in the pull request being updated, merged or declined. This is a readonly operation; the pull request
     * refs are not updated in the repository.
     *
     * @param parameters provides the rescopes and describes what detail information to return
     * @param callback   callback that receives the analysis outcome and details for each provided rescope
     */
    void rescope(@Nonnull BatchRescopePullRequestsParameters parameters, @Nonnull PullRequestRescopeCallback callback);

    /**
     * Streams the files that are changed by the commits in the {@link PullRequest pull request} to the provided
     * {@link ChangeCallback}.
     *
     * @param parameters parameters for the pull request changes to stream
     * @param callback   callback that receives changes
     * @throws NoMergeBaseException if {@link StreamPullRequestChangesParameters#isAllowUnrelated} is {@code false}
     *                              and the pull request's from and to refs do not share a common ancestor
     */
    void streamChanges(@Nonnull StreamPullRequestChangesParameters parameters, @Nonnull ChangeCallback callback);

    /**
     * Updates the pull request refs in the repository to be in sync with the provided
     * {@link PullRequest pull request}. More specifically, the dark refs are updated to be equal to the provided
     * {@code from} and {@code to} hashes. The <em>public</em> refs are either updated if the pull request is open,
     * or deleted if the pull request has been merged or declined.
     *
     * @param pullRequest the pull request to update
     * @return the <em>public</em> pull request refs that were updated
     */
    @Nonnull
    List<RefUpdate> updateRefs(@Nonnull PullRequest pullRequest);

    /**
     * Updates the pull request refs in the repository to be in sync with the provided
     * {@link PullRequest pull requests}. More specifically, the dark refs are updated to be equal to the provided
     * {@code from} and {@code to} hashes. The <em>public</em> refs are either updated if the pull request is open,
     * or deleted if the pull request has been merged or declined.
     *
     * @param pullRequests the pull requests to update
     * @return the <em>public</em> pull request refs that were updated, for each pull request that had at least 1 ref
     *         updated
     */
    @Nonnull
    List<PullRequestRefUpdates> updateRefs(@Nonnull Collection<PullRequest> pullRequests);
}
