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

import com.atlassian.bitbucket.mesh.git.GitProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.git.exception.InvalidBranchNameException;
import com.atlassian.bitbucket.mesh.git.exception.RefNameConflictException;
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;
import static com.atlassian.bitbucket.mesh.rpc.v1.RpcInvalidBranchName.Reason.*;
import static java.util.Objects.requireNonNull;

public class CreateBranchCompletionHandler extends GitProcessCompletionHandler {

    // Git versions 2.35+ use a lowercase "a" and remove the full stop
    private static final Pattern PATTERN_BRANCH_ALREADY_EXISTS = Pattern.compile("fatal: [Aa] branch named '(.+)' already exists\\.?");
    // Git versions 2.35+ remove the full stop
    private static final Pattern PATTERN_INVALID_BRANCH_NAME = Pattern.compile("fatal: '(.+)' is not a valid branch name\\.?");
    // Git versions 2.35+ use a lowercase "n" and remove the full stop
    private static final Pattern PATTERN_INVALID_BRANCH_POINT = Pattern.compile("fatal: [Nn]ot a valid branch point: '(.+)'\\.?");

    // This occurs if for example branch "release/v1.0" exists and an attempt to create "release" is made
    private static final Pattern PATTERN_BRANCH_FILE_DIR_CONFLICT = Pattern.compile("error: there are still refs under '(.+)'");
    // This occurs if for example branch "release" exists and an attempt to create "release/v1.0" is made
    private static final Pattern PATTERN_BRANCH_DIR_FILE_CONFLICT = Pattern.compile("error: unable to resolve reference ([^:]+): Not a directory");
    // the error looks differently in case of branching, so we can't rely on parent class for that error
    // Git versions 2.35+ use a lowercase "n" and remove the full stop
    private static final Pattern PATTERN_BRANCH_POINT_NOT_FOUND = Pattern.compile("fatal: [Nn]ot a valid object name: '(.+)'\\.?");
    // The above two patterns match the error for loose refs. Git has a different code-path for detecting conflicting refs
    // for packed refs. This handles both the above cases, for packed refs.
    private static final Pattern PATTERN_PACKED_BRANCH_CONFLICT = Pattern.compile("error: '(.+)' exists; cannot create '(.+)'.*");
    /**
     * Git 2.4.3 unified the error messaging for the three "CONFLICT" patterns above, normalizing it to a lock
     * failure message in all cases. Now, regardless of direction or pack status, the error message is always
     * the same. It also changed the level from "error" to "fatal".
     * <p>
     * In 2.5.0-rc0 the error message was changed again, lower-casing the "c" in "cannot".
     */
    private static final Pattern PATTERN_LOCK_FAILED = Pattern.compile("fatal: cannot lock ref '(.+)': '(.+)' exists; cannot create '(.+)'",
            Pattern.CASE_INSENSITIVE);

    @Override
    protected void evaluateStderr(@Nonnull ProcessSummary summary, @Nonnull String stderr) {
        super.evaluateStderr(summary, stderr);

        //Some error output may be multi-line for git branch. E.g. when the start point is a blob not a commit.
        String[] lines = stderr.split("\\n");
        for (String line : lines) {
            Reason reason = null;
            String branchName = null;

            Matcher matcher = PATTERN_BRANCH_POINT_NOT_FOUND.matcher(line);
            if (matcher.matches()) {
                reason = START_POINT_NOT_FOUND;
                branchName = matcher.group(1);
            } else if ((matcher = PATTERN_INVALID_BRANCH_NAME.matcher(line)).matches()) {
                reason = NAME_INVALID;
                branchName = matcher.group(1);
            } else if ((matcher = PATTERN_BRANCH_ALREADY_EXISTS.matcher(line)).matches()) {
                reason = DUPLICATE;
                branchName = matcher.group(1);
            } else if ((matcher = PATTERN_INVALID_BRANCH_POINT.matcher(line)).matches()) {
                reason = START_POINT_INVALID;
                branchName = matcher.group(1);
            } else if ((matcher = PATTERN_BRANCH_FILE_DIR_CONFLICT.matcher(line)).matches()) {
                throw new RefNameConflictException(matcher.group(1));
            } else if ((matcher = PATTERN_BRANCH_DIR_FILE_CONFLICT.matcher(line)).matches()) {
                throw new RefNameConflictException(matcher.group(1));
            } else if ((matcher = PATTERN_PACKED_BRANCH_CONFLICT.matcher(line)).matches()) {
                throw new RefNameConflictException(matcher.group(2), matcher.group(1));
            } else if ((matcher = PATTERN_LOCK_FAILED.matcher(line)).matches()) {
                //Git 2.4.3+. 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));
            }

            if (reason != null) {
                throw new InvalidBranchNameException(reason, requireNonNull(branchName, "branchName"));
            }
        }
    }
}
