//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.cloud.network.nicira;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import com.cloud.utils.rest.BasicRestClient;
import com.cloud.utils.rest.CloudstackRESTException;
import com.cloud.utils.rest.HttpConstants;
import com.cloud.utils.rest.HttpMethods;
import com.cloud.utils.rest.HttpStatusCodeHelper;
import com.cloud.utils.rest.HttpUriRequestBuilder;

public class NiciraRestClient extends BasicRestClient {

    private static final Logger s_logger = Logger.getLogger(NiciraRestClient.class);

    private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE;
    private static final String TEXT_HTML_CONTENT_TYPE = HttpConstants.TEXT_HTML_CONTENT_TYPE;

    private static final int DEFAULT_BODY_RESP_MAX_LEN = 1024;
    private static final int DEFAULT_EXECUTION_LIMIT = 5;

    private final ExecutionCounter counter;
    private final int maxResponseErrorMesageLength;
    private final int executionLimit;

    private final String username;
    private final String password;
    private final String loginUrl;

    private NiciraRestClient(final Builder builder) {
        super(builder.client, builder.clientContext, builder.hostname);
        executionLimit = builder.executionLimit;
        counter = new ExecutionCounter(executionLimit);
        maxResponseErrorMesageLength = builder.maxResponseErrorMesageLength;
        username = builder.username;
        password = builder.password;
        loginUrl = builder.loginUrl;
    }

    public static Builder create() {
        return new Builder();
    }

    @Override
    public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException {
        return execute(request, 0);
    }

    private CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException {
        if (counter.hasReachedExecutionLimit()) {
            throw new CloudstackRESTException("Reached max executions limit of " + executionLimit);
        }
        counter.incrementExecutionCounter();
        s_logger.debug("Executing " + request.getMethod() + " request [execution count = " + counter.getValue() + "]");
        final CloseableHttpResponse response = super.execute(request);

        final StatusLine statusLine = response.getStatusLine();
        final int statusCode = statusLine.getStatusCode();
        s_logger.debug("Status of last request: " + statusLine.toString());
        if (HttpStatusCodeHelper.isUnauthorized(statusCode)) {
            return handleUnauthorizedResponse(request, previousStatusCode, response, statusCode);
        } else if (HttpStatusCodeHelper.isSuccess(statusCode)) {
            return handleSuccessResponse(request, response);
        } else if (HttpStatusCodeHelper.isConflict(statusCode)) {
            throw new CloudstackRESTException("Conflict: " + statusLine.getReasonPhrase(), statusCode);
        } else {
            throw new CloudstackRESTException("Unexpected status code: " + statusCode, statusCode);
        }
    }

    private CloseableHttpResponse handleUnauthorizedResponse(final HttpUriRequest request, final int previousStatusCode, final CloseableHttpResponse response, final int statusCode)
                    throws CloudstackRESTException {
        super.closeResponse(response);
        if (HttpStatusCodeHelper.isUnauthorized(previousStatusCode)) {
            s_logger.error(responseToErrorMessage(response));
            throw new CloudstackRESTException("Two consecutive failed attempts to authenticate against REST server");
        }
        final HttpUriRequest authenticateRequest = createAuthenticationRequest();
        final CloseableHttpResponse loginResponse = execute(authenticateRequest, statusCode);
        final int loginStatusCode = loginResponse.getStatusLine().getStatusCode();
        super.closeResponse(loginResponse);
        return execute(request, loginStatusCode);
    }

    private CloseableHttpResponse handleSuccessResponse(final HttpUriRequest request, final CloseableHttpResponse response) {
        if (!request.getURI().getPath().contains(loginUrl)) {
            counter.resetExecutionCounter();
        }
        return response;
    }

    private HttpUriRequest createAuthenticationRequest() {
        final Map<String, String> parameters = new HashMap<>();
        parameters.put("username", username);
        parameters.put("password", password);
        return HttpUriRequestBuilder.create()
            .method(HttpMethods.POST)
            .methodParameters(parameters)
            .path(loginUrl)
            .build();
    }

    private String responseToErrorMessage(final CloseableHttpResponse response) {
        String errorMessage = response.getStatusLine().toString();
        if (response.containsHeader(CONTENT_TYPE) && TEXT_HTML_CONTENT_TYPE.equals(response.getFirstHeader(CONTENT_TYPE).getValue())) {
            try {
                final HttpEntity entity = response.getEntity();
                final String respobnseBody = EntityUtils.toString(entity);
                errorMessage = respobnseBody.subSequence(0, maxResponseErrorMesageLength).toString();
            } catch (final IOException e) {
                s_logger.debug("Could not read repsonse body. Response: " + response, e);
            }
        }

        return errorMessage;
    }

    protected static class Builder extends BasicRestClient.Builder<Builder> {
        private CloseableHttpClient client;
        private HttpClientContext clientContext;
        private String hostname;
        private String username;
        private String password;
        private String loginUrl;
        private int executionLimit = DEFAULT_EXECUTION_LIMIT;
        private int maxResponseErrorMesageLength = DEFAULT_BODY_RESP_MAX_LEN;

        public Builder hostname(final String hostname) {
            this.hostname = hostname;
            return this;
        }

        public Builder username(final String username) {
            this.username = username;
            return this;
        }

        public Builder password(final String password) {
            this.password = password;
            return this;
        }

        public Builder loginUrl(final String loginUrl) {
            this.loginUrl = loginUrl;
            return this;
        }

        @Override
        public Builder client(final CloseableHttpClient client) {
            this.client = client;
            return this;
        }

        @Override
        public Builder clientContext(final HttpClientContext clientContext) {
            this.clientContext = clientContext;
            return this;
        }

        public Builder executionLimit(final int executionLimit) {
            this.executionLimit = executionLimit;
            return this;
        }

        public Builder maxResponseErrorMesageLength(final int maxResponseErrorMesageLength) {
            this.maxResponseErrorMesageLength = maxResponseErrorMesageLength;
            return this;
        }

        @Override
        public NiciraRestClient build() {
            return new NiciraRestClient(this);
        }

    }
}
