package com.sap.cloud.cf.monitoring.client;

import static com.sap.cloud.cf.monitoring.client.utils.Utils.checkNotNull;
import static java.lang.String.format;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.sap.cloud.cf.monitoring.client.configuration.ConfigurationProvider;
import com.sap.cloud.cf.monitoring.client.exceptions.MonitoringClientException;
import com.sap.cloud.cf.monitoring.client.model.Metric;
import com.sap.cloud.cf.monitoring.client.model.api.MetricRequest;

public class MonitoringClientBuilder {
    private static final Logger LOG = LoggerFactory.getLogger(MonitoringClientBuilder.class);

    private ConfigurationProvider provider;
    private CloseableHttpClient client = null;

    public MonitoringClientBuilder setConfigurationProvider(ConfigurationProvider provider) {
        checkNotNull(provider, "configuration provider");
        this.provider = provider;
        return this;
    }

    public MonitoringClientBuilder setHttpClient(CloseableHttpClient client) {
        checkNotNull(client, "http client");
        this.client = client;
        return this;
    }

    public MonitoringClient create() {
        if (this.client == null) {
            return new MonitoringClientImpl(this.provider);
        }
        return new MonitoringClientImpl(this.provider, this.client);
    }

    static class MonitoringClientImpl implements MonitoringClient {

        static final String REQUEST_URL_TEMPLATE = "%s/v1/apps/%s/instances/%s";

        private static final Gson gson = new Gson();

        private ConfigurationProvider configurationProvider;
        private CloseableHttpClient client;
        private UsernamePasswordCredentials credentials;

        private static CloseableHttpClient createHttpClient(ConfigurationProvider provider) {
            return HttpClients.custom().useSystemProperties().build();
        }

        MonitoringClientImpl(ConfigurationProvider provider) {
            this(provider, createHttpClient(provider));
        }

        MonitoringClientImpl(ConfigurationProvider provider, CloseableHttpClient client) {
            checkNotNull(provider, "configuration provider");
            checkNotNull(client, "client");
            this.configurationProvider = provider;
            this.client = client;
        }

        @Override
        public void send(Metric metric) throws MonitoringClientException {
            checkNotNull(metric, "metric");
            send(Arrays.asList(metric));
        }

        @Override
        public void send(List<Metric> metrics) throws MonitoringClientException {
            checkNotNull(metrics, "metrics");
            if (metrics.isEmpty()) {
                throw new IllegalArgumentException("The list of metrics cannot be empty");
            }
            MetricRequest metricRequest = createMetricRequest(metrics);
            String requestURL = createRequestURL();
            processRequest(requestURL, metricRequest);
        }

        private void processRequest(String requestURL, MetricRequest metricRequest) {
            HttpPost request = new HttpPost(requestURL);
            request.setEntity(createEntity(metricRequest));
            request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
            request.addHeader(basicSchema(request));

            try (CloseableHttpResponse response = client.execute(request)) {
                checkResponseCode(response);
                EntityUtils.consumeQuietly(response.getEntity());
            } catch (IOException e) {
                throw new MonitoringClientException(
                        "Error occured while trying to send metrics to the monitoring service", e);
            }
        }

        private Header basicSchema(HttpPost request) {
            try {
                return new BasicScheme().authenticate(getCredentials(configurationProvider), request, null);
            } catch (AuthenticationException e) {
                LOG.error("Could not initialize BasicSchema");
            }
            return null;
        }

        private UsernamePasswordCredentials getCredentials(ConfigurationProvider provider) {
            if (credentials == null) {
                credentials =
                    new UsernamePasswordCredentials(provider.getClientId(), new String(provider.getClientSecret()));
            }
            return credentials;
        }

        private void checkResponseCode(CloseableHttpResponse response) {
            int statusCode = response.getStatusLine().getStatusCode();
            if (HttpStatus.SC_CREATED != statusCode) {
                String message = extractErrorMessage(response);
                throw new MonitoringClientException(format(
                    "Unexpected response code from monitoring service: %d, message: %s", statusCode, message));
            }
        }

        private String extractErrorMessage(CloseableHttpResponse response) {
            try {
                return EntityUtils.toString(response.getEntity());
            } catch (Exception e) { //NOSONAR
                LOG.warn("Cannot extract the error message from the response", e);
                return "";
            }
        }

        private HttpEntity createEntity(MetricRequest metricRequest) {
            try {
                return new StringEntity(gson.toJson(metricRequest));
            } catch (UnsupportedEncodingException e) {
                throw new MonitoringClientException("Unable to create request entity", e);
            }
        }

        private String createRequestURL() {
            return String.format(REQUEST_URL_TEMPLATE, configurationProvider.getUrl(),
                configurationProvider.getApplicationGUID(), configurationProvider.getInstanceGUID());
        }

        private MetricRequest createMetricRequest(List<Metric> metrics) {
            return new MetricRequest(configurationProvider.getApplicationGUID(),
                    configurationProvider.getInstanceGUID(), configurationProvider.getInstanceIndex(), metrics);
        }
    }
}