package com.atlassian.stash.internal.scm.git.protocol.http;

import com.atlassian.stash.internal.scm.git.protocol.RepositoryPath;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Optional.empty;
import static org.apache.commons.lang3.StringUtils.defaultString;

/**
 * Encapsulates the path info of an HTTP SCM request, providing methods that help the client
 * interrogate the contents of the request.
 */
class PathInfo {

    private final String relativePath;
    private final Optional<RepositoryPath> repositoryPath;
    private final RequestCategory requestCategory;

    private PathInfo(Optional<RepositoryPath> repositoryPath, String relativePath, RequestCategory requestCategory) {
        this.repositoryPath = repositoryPath;
        this.relativePath = relativePath;
        this.requestCategory = requestCategory;
    }

    @Nonnull
    public static PathInfo of(String path) {
        if (path != null) {
            for (PathInfoMatcher matcher : PathInfoMatcher.values()) {
                Optional<PathInfo> info = matcher.parse(path);
                if (info.isPresent()) {
                    return info.get();
                }
            }
        }

        return new PathInfo(empty(), defaultString(path), RequestCategory.UNKNOWN);
    }

    @Nonnull
    public String getRelativePath() {
        return relativePath;
    }

    @Nonnull
    public Optional<RepositoryPath> getRepositoryPath() {
        return repositoryPath;
    }

    public boolean isDumb() {
        return requestCategory == RequestCategory.DUMB;
    }

    public boolean isReceive() {
        return requestCategory == RequestCategory.RECEIVE_PACK;
    }

    public boolean isUpload() {
        return requestCategory == RequestCategory.UPLOAD_PACK;
    }

    private enum RequestCategory {
        RECEIVE_PACK,
        UPLOAD_PACK,
        DUMB,
        UNKNOWN
    }

    private enum PathInfoMatcher {
        RECEIVE_PACK("/git-receive-pack$", RequestCategory.RECEIVE_PACK),
        UPLOAD_PACK("/git-upload-pack$", RequestCategory.UPLOAD_PACK),
        HEAD("/HEAD$", RequestCategory.DUMB),
        REFS("/info/refs$", RequestCategory.DUMB),
        OBJECTS("/objects/[0-9a-f]{2}/[0-9a-f]{38}$", RequestCategory.DUMB),
        ALTERNATES("/objects/info/alternates$", RequestCategory.DUMB),
        HTTP_ALTERNATES("/objects/info/http-alternates$", RequestCategory.DUMB),
        PACK_LIST("/objects/info/packs$", RequestCategory.DUMB),
        INDEX("/objects/pack/pack-[0-9a-f]{40}\\.idx$", RequestCategory.DUMB),
        PACK("/objects/pack/pack-[0-9a-f]{40}\\.pack$", RequestCategory.DUMB),
        ANYTHING_ELSE("$", RequestCategory.UNKNOWN);

        private static final Pattern PATTERN_SLASH = Pattern.compile("/");

        private final Pattern suffixMatcher;
        private final RequestCategory requestCategory;

        PathInfoMatcher(String suffixMatcher, RequestCategory requestCategory) {
            this.suffixMatcher = Pattern.compile(suffixMatcher);
            this.requestCategory = requestCategory;
        }

        /**
         * Parses the request path info and returns an instance of {@link PathInfo} describing it but only if it matches
         * this matcher expectations
         */
        @Nonnull
        Optional<PathInfo> parse(@Nonnull String pathInfo) {
            checkNotNull(pathInfo, "pathInfo");

            Matcher matcher = suffixMatcher.matcher(pathInfo);
            if (matcher.find()) {
                String remainingPathInfo = matcher.group(0);
                StringBuffer repositoryAddress = new StringBuffer();
                matcher.appendReplacement(repositoryAddress, "");

                // take first / off so split doesn't start with an empty element
                List<String> addressSegments = Arrays.asList(PATTERN_SLASH.split(StringUtils.removeStart(repositoryAddress.toString(), "/")));

                int segmentCount = addressSegments.size();
                boolean hasEmpty = addressSegments.stream().anyMatch(StringUtils::isBlank);

                if (segmentCount >= 2 && segmentCount <= 3 && !hasEmpty) {
                    String namespace;
                    String projectKey;
                    String repoSlug;

                    if (segmentCount == 2) {
                        namespace = null;
                        projectKey = addressSegments.get(0);
                        repoSlug = addressSegments.get(1);
                    } else {
                        namespace = addressSegments.get(0);
                        projectKey = addressSegments.get(1);
                        repoSlug = addressSegments.get(2);
                    }

                    // strip off .git if it's there
                    repoSlug = StringUtils.removeEnd(repoSlug, ".git");

                    return Optional.of(new PathInfo(Optional.of(new RepositoryPath(namespace, projectKey, repoSlug)),
                            remainingPathInfo, requestCategory));
                }
            }

            return Optional.empty();
        }
    }
}

