/*
 * Copyright 2016 ThoughtWorks, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.thoughtworks.go.agent;

import com.thoughtworks.go.agent.common.ssl.GoAgentServerHttpClient;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.protocol.BasicHttpContext;
import org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor;
import org.springframework.remoting.httpinvoker.HttpInvokerClientConfiguration;
import org.springframework.remoting.support.RemoteInvocationResult;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

public class GoHttpClientHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
    private final GoAgentServerHttpClient goAgentServerHttpClient;
    private final SystemEnvironment environment;

    public GoHttpClientHttpInvokerRequestExecutor(GoAgentServerHttpClient goAgentServerHttpClient, SystemEnvironment environment) {
        this.goAgentServerHttpClient = goAgentServerHttpClient;
        this.environment = environment;
    }

    @Override
    protected RemoteInvocationResult doExecuteRequest(HttpInvokerClientConfiguration config, ByteArrayOutputStream baos) throws Exception {
        HttpPost postMethod = new HttpPost(config.getServiceUrl());

        ByteArrayEntity entity = new ByteArrayEntity(baos.toByteArray());
        entity.setContentType(getContentType());
        postMethod.setEntity(entity);

        BasicHttpContext context = null;

        if (environment.useSslContext()) {
            context = new BasicHttpContext();
            context.setAttribute(HttpClientContext.USER_TOKEN, goAgentServerHttpClient.principal());
        }

        try (CloseableHttpResponse response = goAgentServerHttpClient.execute(postMethod, context)) {
            validateResponse(response);
            InputStream responseBody = getResponseBody(response);
            return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
        }
    }

    private InputStream getResponseBody(HttpResponse httpResponse) throws IOException {
        if (isGzipResponse(httpResponse)) {
            return new GZIPInputStream(httpResponse.getEntity().getContent());
        } else {
            return httpResponse.getEntity().getContent();
        }
    }

    private boolean isGzipResponse(HttpResponse httpResponse) {
        Header encodingHeader = httpResponse.getFirstHeader(HTTP_HEADER_CONTENT_ENCODING);
        return (encodingHeader != null && encodingHeader.getValue() != null && encodingHeader.getValue().toLowerCase().contains(ENCODING_GZIP));
    }

    private void validateResponse(HttpResponse response) throws IOException {
        StatusLine status = response.getStatusLine();
        if (status.getStatusCode() >= 300) {
            throw new NoHttpResponseException("Did not receive successful HTTP response: status code = " + status.getStatusCode() + ", status message = [" + status.getReasonPhrase() + "]");
        }

    }
}

@@ -903,16 +903,71 @@
 usLine;%0A
+import org.apache.http.client.ClientProtocolException;%0A
 import o
@@ -1214,32 +1214,89 @@
 sicHttpContext;%0A
+import org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;%0A
 import org.sprin
@@ -1598,32 +1598,80 @@
 io.InputStream;%0A
+import java.util.Arrays;%0Aimport java.util.List;%0A
 import java.util
@@ -1790,16 +1790,126 @@
 cutor %7B%0A
+    private static final Logger LOG = LoggerFactory.getLogger(GoHttpClientHttpInvokerRequestExecutor.class);%0A%0A
     priv
@@ -3937,24 +3937,781 @@
 atusLine();%0A
+        if (status.getStatusCode() == 403) %7B%0A            String messagePrefix = %22The server returned status code 403. Possible reasons include:%22;%0A%0A            List%3CString%3E reasons = Arrays.asList(%0A                    %22This agent has been deleted from the configuration%22,%0A                    %22This agent is pending approval%22,%0A                    %22There is possibly a reverse proxy (or load balancer) that is terminating SSL. Hint: use port 8154 of the GoCD server. See https://docs.gocd.org/current/installation/configure-reverse-proxy.html#agents-and-reverse-proxies for details.%22%0A            );%0A%0A            String delimiter = %22%5Cn   - %22;%0A            throw new ClientProtocolException(messagePrefix + delimiter + String.join(delimiter, reasons));%0A        %7D%0A
         if (

Improve the error message when agent gets 403