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

import com.atlassian.bitbucket.mesh.git.GitProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.git.exception.InvalidTagNameException;
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.RpcInvalidTagName.Reason.*;

public class CreateTagCompletionHandler extends GitProcessCompletionHandler {

    private static final Pattern PATTERN_INVALID_START_POINT =
            Pattern.compile("fatal: Failed to resolve '(.+)' as a valid ref.$", Pattern.MULTILINE);
    private static final Pattern PATTERN_INVALID_TAG_NAME =
            Pattern.compile("fatal: '(.+)' is not a valid tag name.$", Pattern.MULTILINE);
    private static final Pattern PATTERN_TAG_ALREADY_EXISTS =
            Pattern.compile("fatal: tag '(.+)' already exists$", Pattern.MULTILINE);

    // This occurs if for example tag "release/v1.0" exists and an attempt to create "release" is made
    private static final Pattern PATTERN_TAG_FILE_DIR_CONFLICT = Pattern.compile("error: there are still refs under '(.+)'");
    // This occurs if for example tag "release" exists and an attempt to create "release/v1.0" is made
    private static final Pattern PATTERN_TAG_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_TAG_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);

        Matcher matcher = PATTERN_INVALID_START_POINT.matcher(stderr);
        if (matcher.find()) {
            String startPoint = matcher.group(1);
            throw new InvalidTagNameException(START_POINT_NOT_FOUND, startPoint);
        }

        matcher = PATTERN_INVALID_TAG_NAME.matcher(stderr);
        if (matcher.find()) {
            String name = matcher.group(1);
            throw new InvalidTagNameException(NAME_INVALID, name);
        }

        matcher = PATTERN_TAG_ALREADY_EXISTS.matcher(stderr);
        if (matcher.find()) {
            String name = matcher.group(1);
            throw new InvalidTagNameException(DUPLICATE, name);
        }

        matcher = PATTERN_TAG_FILE_DIR_CONFLICT.matcher(stderr);
        if (matcher.find()) {
            throw new RefNameConflictException(matcher.group(1));
        }

        matcher = PATTERN_TAG_DIR_FILE_CONFLICT.matcher(stderr);
        if (matcher.find()) {
            throw new RefNameConflictException(matcher.group(1));
        }

        matcher = PATTERN_PACKED_TAG_CONFLICT.matcher(stderr);
        if (matcher.find()) {
            throw new RefNameConflictException(matcher.group(2), matcher.group(1));
        }

        matcher = PATTERN_LOCK_FAILED.matcher(stderr);
        if (matcher.find()) {
            //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));
        }
    }

}