package com.atlassian.stash.internal.scm.git.pull;

import com.atlassian.bitbucket.scm.git.GitUtils;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Ref;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.util.Timer;
import com.atlassian.bitbucket.util.TimerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.File;

import static com.atlassian.bitbucket.scm.git.GitUtils.refsMatch;

/**
 * @since 2.9
 */
public class ExistingPullRequestAutoMergeStrategy implements PullRequestAutoMergeStrategy {

    private static final Logger log = LoggerFactory.getLogger(ExistingPullRequestAutoMergeStrategy.class);

    private final GitAgent agent;

    public ExistingPullRequestAutoMergeStrategy(GitAgent agent) {
        this.agent = agent;
    }

    @Override
    public PullRequestMerge merge(@Nonnull PullRequestAutoMergeRequest request, @Nonnull PullRequestRefs refs) {
        try (Timer timer = TimerUtils.start("git: resolve existing merge " + request)) {
            if (refsMatch(request.getFromRef(), refs.resolveFrom()) &&
                    refsMatch(request.getToRef(), refs.resolveTo())) {
                //The refs are up-to-date, so the existing merge is correct for the current effective diff.
                //However, we still need to determine the "type" of merge that it is so we can produce the
                //correct effective diff.
                return resolveMerge(request);
            }

            return null;
        }
    }

    /**
     * Searches across the various {@link PullRequestMergeType merge types} to find the one which exists for the
     * pull request and returns a {@link PullRequestMerge merge} with the resolved type and hash.
     * <p>
     * This method is called after the {@link PullRequestRefs#getFrom() from} and {@link PullRequestRefs#getTo() to}
     * refs have been resolved and {@link GitUtils#refsMatch(Ref, Ref) matched},
     * indicating the merge for a previous auto-merge should be available. If it isn't, a warning will be logged and
     * {@code null} will be returned.
     *
     * @return the resolved merge, or {@code null} if the expected merge could not be resolved
     */
    private PullRequestMerge resolveMerge(PullRequestAutoMergeRequest request) {
        PullRequestRefBuilder builder = PullRequestRefBuilder.darkRef()
                .id(request.getPullRequest());
        File repositoryDir = request.getRepositoryDir();

        for (PullRequestMergeType mergeType : PullRequestMergeType.values()) {
            //Due to the two-phase upgrade task to eliminate pull request refs there is a possibility that, _briefly_,
            //the contents of the symbolic refs identifying the merge type are invalid. To ensure that doesn't prevent
            //resolving existing merges, just do an exists check on the symbolic ref's file and then resolve the merge
            //directly (so that the correct name for the merge will be used even if the symbolic ref is invalid)
            if (builder.name(mergeType.getRefName()).toFile(repositoryDir).isFile()) {
                Branch merge = agent.resolveBranch(request.getToRepository(), builder.type(PullRequestRefType.MERGE).toRef(), true);
                if (merge != null) {
                    log.trace("{}: A {} merge exists for pull request {}; using for effective diff",
                            request.getToRepository().getId(), mergeType, request);

                    return new PullRequestMerge(merge.getLatestCommit(), mergeType);
                }
            }
        }

        log.warn("{}: After matching the from and to refs, the expected merge could not be resolved for pull request " +
                "{}. The merge will be re-done. This may result in lost comments and other strange behaviours.",
                request.getToRepository().getId(), request);
        return null;
    }
}
