package com.atlassian.stash.internal.scm.git.command.merge;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.GitRefPattern;
import com.atlassian.bitbucket.scm.git.command.GitCommandExitHandler;
import com.atlassian.bitbucket.scm.git.command.push.GitNonFastForwardUpdateRejectedException;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Inspects the stderr of a {@code git push} command (as part of a merge - either an explicit merge by the user or a
 * merge used for generating diffs) and throws a {@link GitNonFastForwardUpdateRejectedException} when it detects the ref
 * update was rejected.
 *
 * @see FetchCommandExitHandler
 * @since 1.3
 */
public class PushCommandExitHandler extends GitCommandExitHandler {

    private static final String FAILED_TO_PUSH = "error: failed to push some refs to";
    private static final String NON_FF_UPDATES_REJECTED = "non-fast-forward updates were rejected";

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

    private final boolean forced;
    private final String ref;

    public PushCommandExitHandler(@Nonnull I18nService i18nService, @Nonnull Repository repository,
                                  @Nonnull String ref, boolean forced) {
        super(i18nService, checkNotNull(repository, "repository"));

        this.forced = forced;
        this.ref = checkNotNull(ref, "ref");
    }

    /**
     * Evaluates the contents of stderr. If stderr does not suggest the push failed then default processing is
     * performed as defined in {@link GitCommandExitHandler#evaluateStdErr(String, String)}}. If stderr does suggest
     * push failed then how it responds depends on the current push mode:
     * <p>
     * If the push was {@link #forced} then it is highly likely that a git hook is vetoing the ref update, so we log
     * a very specific message to help support diagnose the odd diff behaviour the user will see and then throw a
     * {@link GitNonFastForwardUpdateRejectedException}.
     * <p>
     * Otherwise, it is highly likely the repository's target branch was changed during the merge process and so we
     * throw a {@link GitNonFastForwardUpdateRejectedException} for the infrastructure higher up to handle as appropriate.
     *
     * @param stdErr  the stderr contents to inspect
     * @param command the push command that was executed
     * @throws GitNonFastForwardUpdateRejectedException if a {@link #forced} push failed, or if a normal push was
     *                                               rejected because it was not fast-forward
     */
    @Override
    protected void evaluateStdErr(String stdErr, String command) {
        if (stdErr.contains(FAILED_TO_PUSH)) {
            if (forced) {
                logForcedPushRejected(stdErr, command);
                throw newNonFastForwardUpdateRejected();
            }
            if (stdErr.contains(NON_FF_UPDATES_REJECTED)) {
                //Merging is non-forced and non-ff updates have just been rejected
                throw newNonFastForwardUpdateRejected();
            }
        }

        super.evaluateStdErr(stdErr, command);
    }

    @VisibleForTesting
    protected void logForcedPushRejected(String stdErr, String command) {
        log.error("Merge: A forced update to {} was rejected by git. Please ensure any git hooks for {}/{} " +
                "do not reject non-fast-forward updates under {} as doing so may interfere with some pull " +
                "request operations, such as displaying diffs.\n\"{}\" failed with:\n{}",
                ref, repository.getProject().getKey(), repository.getSlug(),
                GitRefPattern.PULL_REQUESTS.getPath(), command, stdErr);
    }

    private GitNonFastForwardUpdateRejectedException newNonFastForwardUpdateRejected() {
        throw new GitNonFastForwardUpdateRejectedException(i18nService.createKeyedMessage("bitbucket.git.merge.concurrent"),
                ref);
    }
}
