package com.atlassian.bitbucket.scm.http;

import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Utility class for parsing repository slugs and project keys from scm urls.
 *
 * <p>
 * This class only works with paths that start with: <ul>
 *  <li>&lt;baseUrl&gt;/scm/PROJECT_KEY/REPOSITORY_SLUG/</li>
 *  <li>&lt;baseUrl&gt;/scm/PROJECT_KEY/REPOSITORY_SLUG.git/ (the .git suffix is not captured)</li>
 * </ul>
 * </p>
 */
public class RepositoryUrlFragment {

    private static final String SHORT_HOSTING_URL = "/([^/]+)/([^/]+?)(?:\\.git)?+(?:/.*|$)";
    private static final String NAMESPACED_HOSTING_URL = "/([^/]+)" + SHORT_HOSTING_URL;
    private static final Pattern PATTERN_SHORT_HOSTING_URL = Pattern.compile(SHORT_HOSTING_URL);
    private static final Pattern PATTERN_NAMESPACED_HOSTING_URL = Pattern.compile(NAMESPACED_HOSTING_URL);

    private final String projectNamespace;
    private final String projectKey;
    private final String repositorySlug;

    /**
     * @since 4.2
     */
    private RepositoryUrlFragment(String projectNamespace, String projectKey, String repositorySlug) {
        this.projectNamespace = projectNamespace;
        this.projectKey = projectKey;
        this.repositorySlug = repositorySlug;
    }

    /**
     * Extracts the repository URL fragment from the given path info.
     *
     * @param pathInfo the path info as provided by the {@link HttpServletRequest#getPathInfo} method.
     * @return a new repository URL fragment, or null if the path info does not contain a repository URL fragment
     * @throws NullPointerException if the path info is null
     */
    @Nullable
    public static RepositoryUrlFragment fromPathInfo(@Nonnull String pathInfo) {
        Matcher m = PATTERN_SHORT_HOSTING_URL.matcher(checkNotNull(pathInfo, "pathInfo"));
        return m.find() ? new RepositoryUrlFragment(null, m.group(1), m.group(2)) : null;
    }

    /**
     * Extracts the namespaced repository URL fragment from the given path info.
     *
     * @param pathInfo the path info as provided by the {@link HttpServletRequest#getPathInfo} method.
     * @return a new repository URL fragment, or null if the path info does not contain a namespaced repository URL fragment
     * @throws NullPointerException if the path info is null
     *
     * @since 4.2
     */
    @Nullable
    public static RepositoryUrlFragment fromNamespacedPathInfo(@Nonnull String pathInfo) {
        Matcher m = PATTERN_NAMESPACED_HOSTING_URL.matcher(checkNotNull(pathInfo, "pathInfo"));
        return m.find() ? new RepositoryUrlFragment(m.group(1), m.group(2), m.group(3)) : null;
    }

    /**
     * @param repository the repository to create a fragment for
     * @return a URL fragment for the specified repository
     */
    @Nonnull
    public static RepositoryUrlFragment fromRepository(@Nonnull Repository repository) {
        Project project = checkNotNull(repository, "repository").getProject();
        String projectNamespace = checkNotNull(project, "repository.project").getNamespace();
        String projectKey = project.getKey();

        return new RepositoryUrlFragment(projectNamespace, projectKey, repository.getSlug());
    }

    @Nonnull
    public String getProjectKey() {
        return projectKey;
    }

    @Nullable
    public String getProjectNamespace() {
        return projectNamespace;
    }

    @Nonnull
    public String getRepositorySlug() {
        return repositorySlug;
    }

    /**
     * Formats this fragment as a path suitable for use with a {@code URI}, such as with {@code URI.resolve(String)},
     * optionally prepending a provided base path.
     *
     * @param basePath the base path to prepend, or {@code null}
     * @return a URI-suitable path
     */
    @Nonnull
    public String toPath(@Nullable String basePath) {
        StringBuilder builder = new StringBuilder();
        if (basePath == null) {
            builder.append('/');
        } else {
            builder.append(basePath);
            if (!basePath.endsWith("/")) {
                builder.append('/');
            }
        }
        return builder.append(toPath()).toString();
    }

    /**
     * @return {@link #getProjectKey() KEY}/{@link #getRepositorySlug() slug} for URLs on primary instances,
     *         {@link #getProjectNamespace()}() NAMESPACE}/{@link #getProjectKey() KEY}/{@link #getRepositorySlug() slug} on mirrors
     */
    @Nonnull
    @Override
    public String toString() {
        return toPath();
    }

    private String toPath() {
        String path = projectNamespace == null
                ? projectKey + "/" + repositorySlug
                : projectNamespace + "/" + projectKey + "/" + repositorySlug;
        return path.toLowerCase(Locale.US);
    }
}
