package com.atlassian.bitbucket.internal.scm.git.lfs.ssh;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.internal.scm.git.lfs.BadBatchRequestException;
import com.atlassian.bitbucket.internal.scm.git.lfs.jwt.GitLfsJwtHelper;
import com.atlassian.bitbucket.internal.scm.git.lfs.settings.GitLfsSettingsService;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.repository.NoSuchRepositoryException;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.scm.ssh.ExitCodeCallback;
import com.atlassian.bitbucket.scm.ssh.SshScmRequest;
import com.atlassian.bitbucket.scm.ssh.SshScmRequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.Optional.empty;
import static java.util.Optional.of;

/**
 * SSH handler for Git LFS.
 * <p>
 * Commands will be of the form:
 * <pre>
 *     git-lfs-authenticate project_1/rep_1.git download f234214ed45b33470cbe66505e116b19d3eb46787cf3cc4f7e8f13ee107ff897
 * </pre>
 */
public class GitLfsSshScmRequestHandler implements SshScmRequestHandler {

    private static final String GIT_LFS_AUTHENTICATE_COMMAND = "git-lfs-authenticate";
    private static final String GIT_LFS_PREFIX = "git-lfs-";

    // Matches the arguments to the git-lfs-authenticate command
    private static final String NAMESPACE = "((?<namespace>[^/']+)/)?";
    private static final String PROJECT = "(?<project>[^/]+)/";
    private static final String REPO = "(?<repo>[^/']+?)";
    private static final String EXT = "(?:\\.git)?";
    private static final Pattern PATTERN_NAMESPACE_PROJECT_REPOSITORY = Pattern.compile(String.format(
            "%s%s%s%s", NAMESPACE, PROJECT, REPO, EXT));

    private static final Logger log = LoggerFactory.getLogger(GitLfsSshScmRequestHandler.class);

    private final I18nService i18nService;
    private final GitLfsJwtHelper jwtHelper;
    private final NavBuilder navBuilder;
    private final RepositoryService repositoryService;
    private final GitLfsSettingsService settingsService;

    public GitLfsSshScmRequestHandler(I18nService i18nService, GitLfsJwtHelper jwtHelper, NavBuilder navBuilder,
                                      RepositoryService repositoryService, GitLfsSettingsService settingsService) {
        this.i18nService = i18nService;
        this.jwtHelper = jwtHelper;
        this.navBuilder = navBuilder;
        this.repositoryService = repositoryService;
        this.settingsService = settingsService;
    }

    @Nonnull
    @Override
    public Optional<SshScmRequest> create(@Nonnull String remoteCommand, @Nonnull InputStream in,
                                          @Nonnull OutputStream out, @Nonnull OutputStream err,
                                          @Nonnull ExitCodeCallback exitCodeCallback) {

        if (!supports(remoteCommand) || !settingsService.isEnabled()) {
            return empty();
        }
        String[] tokens = remoteCommand.split("\\s+");
        String command = tokens[0];
        if (GIT_LFS_AUTHENTICATE_COMMAND.equals(command)) {
            log.trace("{} is a supported git command; extracting repository", command);
            if (tokens.length < 2) {
                throw new BadBatchRequestException(i18nService.getMessage(
                        "bitbucket.scm.git.lfs.request.missing.attribute", "repository path"));
            }
            if (tokens.length < 3) {
                throw new BadBatchRequestException(i18nService.getMessage(
                        "bitbucket.scm.git.lfs.request.missing.attribute", "operation"));
            }
            Matcher matcher = PATTERN_NAMESPACE_PROJECT_REPOSITORY.matcher(tokens[1]);
            if (matcher.matches()) {
                String namespace = matcher.group("namespace");
                String projectKey = matcher.group("project");
                String repositorySlug = matcher.group("repo");
                String op = tokens[2];
                if (log.isTraceEnabled()) {
                    log.trace("Extracted namespace {} project key {} repository slug {} operation {}; loading repository",
                            namespace, projectKey, repositorySlug, op);
                }

                Repository repository = repositoryService.getBySlug(namespace, projectKey, repositorySlug);
                if (repository == null) {
                    throw new NoSuchRepositoryException(i18nService.createKeyedMessage(
                            "bitbucket.scm.git.lfs.nosuchrepo", namespace, projectKey, repositorySlug),
                            null);
                }

                // Ideally an "isLfsEnabled" check would occur here, but due to a Git command line client
                // quirk (described in BSERVDEV-11539) we instead pass on the URL and let this check occur
                // on the HTTP request

                boolean write;
                switch (op) {
                    case "download":
                        write = false;
                        break;
                    case "upload":
                        write = true;
                        break;
                    default:
                        throw new BadBatchRequestException(i18nService.getMessage(
                                "bitbucket.scm.git.lfs.request.unsupported.operation", op));
                }
                return of(new GitLfsAuthenticateScmRequest(repository, write, exitCodeCallback,
                        i18nService, jwtHelper, navBuilder, out, settingsService));
            }
        }

        // Proceed to next handler
        return empty();
    }

    @Override
    public boolean supports(@Nonnull String command) {
        return command.startsWith(GIT_LFS_PREFIX);
    }
}