package com.atlassian.bitbucket.mesh.git.builder.updateref;

import com.atlassian.bitbucket.mesh.git.GitProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.git.exception.*;
import com.atlassian.bitbucket.mesh.process.ProcessSummary;

import javax.annotation.Nonnull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.atlassian.bitbucket.mesh.rpc.v1.RpcInvalidBranchName.Reason.DUPLICATE;
import static com.atlassian.bitbucket.mesh.rpc.v1.RpcInvalidRefName.Reason.INVALID;

public class UpdateRefCompletionHandler extends GitProcessCompletionHandler {

    private static final String ERROR_CANNOT_UPDATE_REF = "fatal: cannot update ref '.+': ";
    private static final String ERROR_UPDATE_REF_FAILED = "fatal: update_ref failed for ref '.+': ";
    private static final Pattern PATTERN_INVALID_REF_NAME =
            Pattern.compile(ERROR_UPDATE_REF_FAILED +
                            "refusing to update ref with bad name '(.+)'");
    private static final Pattern PATTERN_NONEXISTENT_OBJECT =
            Pattern.compile(ERROR_CANNOT_UPDATE_REF +
                            "trying to write ref '(.+)' with nonexistent object ([a-f0-9]+)");
    //This pattern uses a reluctant +? for the ref because the trailing ' is optional, unlike the other
    //patterns here. Without the reluctant quantifier a trailing ' is included in the capture group
    private static final Pattern PATTERN_OUT_OF_DATE =
            Pattern.compile("[Rr]ef '?([^:]+?)'?:? is at [a-f0-9]+ but expected [a-f0-9]+");
    private static final Pattern PATTERN_REF_ALREADY_EXISTS =
            Pattern.compile(ERROR_UPDATE_REF_FAILED +
                            "cannot lock ref '([^:]+)': reference already exists");
    //This occurs if for example branch "release" exists and an attempt to create "release/v1.0" is made (and vice versa).
    private static final Pattern PATTERN_REF_CONFLICTING =
            Pattern.compile(ERROR_UPDATE_REF_FAILED +
                            "cannot lock ref '([^:]+)': '(.+)' exists; cannot create '(.+)'");

    private static final Pattern PATTERN_UNABLE_TO_RESOLVE = Pattern.compile(
            ERROR_UPDATE_REF_FAILED + "(?:cannot lock ref '([^:]+)': )?unable to resolve reference '?([^:]+?)'?:?");

    @Override
    protected void evaluateStderr(@Nonnull ProcessSummary summary, @Nonnull String stderr) {
        Matcher matcher = PATTERN_OUT_OF_DATE.matcher(stderr);
        if (matcher.find()) { //Don't use matches(); there's a fatal: segment about locking after the error
            throw new NonFastForwardUpdateException(matcher.group(1));
        }

        matcher = PATTERN_INVALID_REF_NAME.matcher(stderr);
        if (matcher.matches()) {
            throw new InvalidRefNameException(INVALID, matcher.group(1));
        }

        matcher = PATTERN_REF_ALREADY_EXISTS.matcher(stderr);
        if (matcher.matches()) {
            throw new InvalidBranchNameException(DUPLICATE, matcher.group(1));
        }

        matcher = PATTERN_REF_CONFLICTING.matcher(stderr);
        if (matcher.matches()) {
            //The lock failure, printed first, is the new ref. The second is the existing ref.
            //(The third is the new ref again - ignored here)
            throw new RefNameConflictException(matcher.group(1), matcher.group(2));
        }

        matcher = PATTERN_NONEXISTENT_OBJECT.matcher(stderr);
        if (matcher.matches()) {
            throw new CommitNotFoundException(matcher.group(2));
        }

        matcher = PATTERN_UNABLE_TO_RESOLVE.matcher(stderr);
        if (matcher.matches()) {
            String refId = matcher.group(1);
            if (refId == null) {
                //Git 2.2.0 to 2.4.2 output two lines, and Git prior to 2.2.0 only output one, but
                //without the "Cannot lock ref" message. On those versions, we use the second group
                refId = matcher.group(2);
            } else if (!refId.equals(matcher.group(2))) {
                //Git 2.4.3+ merge the two lines into a single line. On those versions, we verify
                //that we parsed out the same ref name on both groups. If they don't, we log a warning
                log.warn("[{}] != [{}]: Unexpected mismatch in ref names after parsing\n{}",
                        refId, matcher.group(2), stderr);
            }
            throw new RefNotFoundException(refId);
        }

        //Otherwise, for any other output, fall back on the default handling
        super.evaluateStderr(summary, stderr);
    }
}