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

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.request.RequestContext;
import com.atlassian.bitbucket.scm.CommandInputHandler;
import com.atlassian.bitbucket.scm.git.GitScmConfig;
import com.atlassian.bitbucket.scm.git.command.GitCommand;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.http.HttpScmRequest;
import com.atlassian.bitbucket.web.cgi.CgiEnvironmentUtils;
import com.atlassian.bitbucket.web.cgi.CgiInputHandler;
import com.atlassian.bitbucket.web.cgi.CgiOutputHandler;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.stash.internal.scm.git.AbstractGitRepositoryScmRequest;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static org.apache.commons.lang3.StringUtils.defaultString;

/**
 * Implementation of {@link HttpScmRequest} for the Git 'smart' protocol over HTTP.
 * <p>
 * For more information about the Git 'smart' protocol, read
 * <a href="http://git-scm.com/2010/03/04/smart-http.html">Smart HTTP Transport</a> and
 * <a href="http://git-scm.com/book/ch9-6.html">Git Internals - Transfer Protocols</a>.
 */
abstract class GitSmartRequest extends AbstractGitRepositoryScmRequest implements HttpScmRequest {

    private static final int DEFAULT_HTTP_BACKEND_BUFFER_SIZE = 32 * 1024; // 32kb default
    // DefaultGitScmConfig automatically prepends PROP_PREFIX to properties
    private static final String PROP_HTTP_BACKEND_BUFFER_SIZE = "backend.http.buffer.size";

    protected final int bufferSize;

    private final EventPublisher eventPublisher;
    private final I18nService i18nService;
    private final HttpServletRequest request;
    private final RequestContext requestContext;
    private final HttpServletResponse response;
    private final ServletContext servletContext;

    public GitSmartRequest(@Nonnull Repository repository, boolean write,
                           @Nonnull GitCommandBuilderFactory builderFactory, @Nonnull GitScmConfig config,
                           @Nonnull EventPublisher eventPublisher, @Nonnull I18nService i18nService,
                           @Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response,
                           @Nonnull ServletContext servletContext, @Nonnull RequestContext requestContext) {
        super(repository, write, builderFactory, config);

        this.eventPublisher = eventPublisher;
        this.i18nService = i18nService;
        this.request = request;
        this.requestContext = requestContext;
        this.response = response;
        this.servletContext = servletContext;

        bufferSize = config.getProperty(PROP_HTTP_BACKEND_BUFFER_SIZE, DEFAULT_HTTP_BACKEND_BUFFER_SIZE);
    }

    @Override
    public void handleRequest() throws IOException {
        try {
            GitScmCommandBuilder builder = builder().command("http-backend")
                    .inputHandler(getInputHandler())
                    .exitHandler(new GitSmartExitHandler(i18nService, getRepository(), request, response, isWrite()) {

                        @Override
                        protected void onSuccess() {
                            // send out an event for push/pull
                            if (isOperationComplete()) {
                                GitSmartRequest.this.onSuccess();
                            }
                        }
                    });
            applyHttpBackendEnvironmentVars(builder);

            GitCommand<Void> command = builder.build(new CgiOutputHandler(response, bufferSize));
            command.setIdleTimeout(config.getBackendIdleTimeout());
            command.setExecutionTimeout(config.getBackendExecutionTimeLimit());
            command.call();
        } finally {
            addLabels(requestContext);
        }
    }

    /**
     * Flushes an error message in a format that the git client will display to the user.
     *
     * @param summary       a short summary (less than 80 chars) of the error
     * @param detailMessage a detailed description of the error
     * @throws IOException if there was a problem writing to the response
     */
    @Override
    public void sendError(@Nonnull String summary, @Nonnull String detailMessage) throws IOException {
        GitHttpUtils.sendError(request, response, summary, detailMessage);
    }

    protected abstract void addLabels(RequestContext requestContext);

    protected void applyHttpBackendEnvironmentVars(GitScmCommandBuilder builder) {
        //From http-backend.c, we have two options:
        //1. Set GIT_PROJECT_ROOT and PATH_INFO, which git will join together with a / inbetween
        //2. Set PATH_TRANSLATED which is used as-is (and, therefore, must be the same as ROOT + PATH_INFO)
        //Since applyStandardCgiEnvironment is going to set PATH_INFO, we might as well just use GIT_PROJECT_ROOT and PATH_INFO
        builder.withEnvironment("GIT_PROJECT_ROOT", config.getRepositoryDir(getRepository()).getPath())
                .withEnvironment("GIT_HTTP_EXPORT_ALL", "true");
        String pathAfterRepository = PathInfo.of(request.getPathInfo()).getRelativePath();
        if (!StringUtils.isEmpty(pathAfterRepository)) {
            builder.withEnvironment("PATH_INFO", defaultString(pathAfterRepository))
                    .withEnvironment("SCRIPT_NAME", defaultString(pathAfterRepository));
        }

        CgiEnvironmentUtils.applyStandardCgiEnvironment(builder, request, servletContext);
    }

    protected int getContentLength() {
        return getRequest().getContentLength();
    }

    protected EventPublisher getEventPublisher() {
        return eventPublisher;
    }

    protected CommandInputHandler getInputHandler() throws IOException {
        return new CgiInputHandler(request.getInputStream(), bufferSize);
    }

    protected HttpServletRequest getRequest() {
        return request;
    }

    protected HttpServletResponse getResponse() {
        return response;
    }

    protected abstract boolean isOperationComplete();

    protected abstract void onSuccess();
}
