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

import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.InvalidNameException;
import com.atlassian.bitbucket.NoSuchObjectException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.scm.git.GitRefPattern;
import com.atlassian.bitbucket.scm.git.command.GitCommandExitHandler;

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

import static com.atlassian.bitbucket.repository.StandardRefType.BRANCH;

public class CreateBranchCommandExitHandler extends GitCommandExitHandler {

    private static final Pattern PATTERN_BRANCH_ALREADY_EXISTS = Pattern.compile("fatal: A branch named '([^']+)' already exists.");
    private static final Pattern PATTERN_BRANCH_POINT_NOT_FOUND = Pattern.compile("fatal: Not a valid object name: '([^']+)'.");
    private static final Pattern PATTERN_INVALID_BRANCH_NAME = Pattern.compile("fatal: '([^']+)' is not a valid branch name.");
    private static final Pattern PATTERN_INVALID_BRANCH_POINT = Pattern.compile("fatal: Not 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 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);

    public CreateBranchCommandExitHandler(@Nonnull I18nService i18nService, @Nonnull Repository repository) {
        super(i18nService, repository);
    }

    @Override
    protected void evaluateStdErr(String stdErr, String command) {
        checkUsage(stdErr, command);

        //Some error output may be mulit-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) {
            Matcher matcher = PATTERN_INVALID_BRANCH_NAME.matcher(line);
            if (matcher.matches()) {
                throw newInvalidBranchName(matcher.group(1));
            }

            matcher = PATTERN_BRANCH_ALREADY_EXISTS.matcher(line);
            if (matcher.matches()) {
                throw newDuplicateBranchName(matcher.group(1));
            }

            matcher = PATTERN_BRANCH_POINT_NOT_FOUND.matcher(line);
            if (matcher.matches()) {
                throw newNoSuchStartingPoint(matcher.group(1));
            }

            matcher = PATTERN_INVALID_BRANCH_POINT.matcher(line);
            if (matcher.matches()) {
                throw newInvalidBranchPoint(matcher.group(1));
            }

            matcher = PATTERN_BRANCH_FILE_DIR_CONFLICT.matcher(line);
            if (matcher.matches()) {
                throw newConflictingBranchName(toMinimalRef(matcher.group(1)));
            }

            matcher = PATTERN_BRANCH_DIR_FILE_CONFLICT.matcher(line);
            if (matcher.matches()) {
                throw newConflictingBranchName(toMinimalRef(matcher.group(1)));
            }

            matcher = PATTERN_PACKED_BRANCH_CONFLICT.matcher(line);
            if (matcher.matches()) {
                throw newConflictingBranchName(toMinimalRef(matcher.group(2)), toMinimalRef(matcher.group(1)));
            }

            matcher = PATTERN_LOCK_FAILED.matcher(line);
            if (matcher.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 newConflictingBranchName(toMinimalRef(matcher.group(1)), toMinimalRef(matcher.group(2)));
            }
        }
    }

    private InvalidRefNameException newInvalidBranchName(String newBranchName) {
        throw new InvalidRefNameException(i18nService.createKeyedMessage("bitbucket.service.repository.branchnameinvalid",
                newBranchName), newBranchName);
    }

    private DuplicateRefException newDuplicateBranchName(String newBranchName) {
        throw new DuplicateRefException(i18nService.createKeyedMessage("bitbucket.service.repository.branch.duplicate",
                newBranchName, repository.getName()), newBranchName);
    }

    private NoSuchObjectException newNoSuchStartingPoint(String startingPoint) {
        throw new NoSuchObjectException(i18nService.createKeyedMessage("bitbucket.service.repository.branch.doesnotexist", startingPoint), startingPoint);
    }

    private InvalidNameException newInvalidBranchPoint(String startingPoint) {
        throw new InvalidNameException(i18nService.createKeyedMessage("bitbucket.service.repository.branch.pointinvalid", startingPoint));
    }

    private ConflictingRefNameException newConflictingBranchName(MinimalRef newBranch) {
        throw new ConflictingRefNameException(i18nService.createKeyedMessage("bitbucket.git.branch.create.looserefconflict",
                newBranch.getDisplayId()), newBranch);
    }

    private ConflictingRefNameException newConflictingBranchName(MinimalRef newBranch, MinimalRef existingBranch) {
        throw new ConflictingRefNameException(i18nService.createKeyedMessage("bitbucket.git.branch.create.packedrefconflict",
                newBranch.getDisplayId(), existingBranch.getDisplayId()), newBranch);
    }

    private MinimalRef toMinimalRef(String id) {
        return new SimpleMinimalRef.Builder()
                .displayId(GitRefPattern.HEADS.unqualify(id))
                .id(id)
                .type(BRANCH)
                .build();
    }
}
