package ru.yandex.webmaster3.core.solomon;

import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.http.HttpConstants;
import ru.yandex.webmaster3.core.http.WebmasterJsonModule;
import ru.yandex.webmaster3.core.metrics.externals.AbstractExternalAPIService;
import ru.yandex.webmaster3.core.metrics.externals.ExternalDependencyMethod;
import ru.yandex.webmaster3.core.solomon.metric.SolomonCommonLabels;
import ru.yandex.webmaster3.core.util.JavaMethodWitness;
import ru.yandex.webmaster3.core.util.environment.YandexEnvironmentProvider;
import ru.yandex.webmaster3.core.util.environment.YandexEnvironmentType;

/**
 * Created by ifilippov5 on 05.05.17.
 */
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SolomonPushAPIService extends AbstractExternalAPIService {
    private static final int SOCKET_TIMEOUT = 20_000;
    public static final String CLUSTER = "cluster";
    public static final String PROJECT = "project";
    public static final String SERVICE = "service";

    private final String oauthKey;
    private final String serviceUrl;
    private final int maxSensors;

    private CloseableHttpClient httpClient;

    private static final ObjectMapper OM = new ObjectMapper()
            .registerModule(new WebmasterJsonModule(false))
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

    public void init() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(HttpConstants.DEFAULT_CONNECTION_REQUEST_TIMEOUT)
                .setConnectTimeout(HttpConstants.DEFAULT_CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT)
                .build();

        httpClient = HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                .build();

    }

    public void destroy() {
        IOUtils.closeQuietly(httpClient);
    }

    @ExternalDependencyMethod("push")
    private boolean _pushMetrics(List<SolomonSensor> sensors, SolomonCommonLabels commonLabels) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            SolomonMessage message = new SolomonMessage(commonLabels, sensors);
            try {
                String data = OM.writeValueAsString(message);
                final URI uri = new URIBuilder(serviceUrl)
                        .addParameter(PROJECT, commonLabels.getProject())
                        .addParameter(CLUSTER, commonLabels.getCluster())
                        .addParameter(SERVICE, commonLabels.getService())
                        .build();
                HttpPost post = new HttpPost(uri);
                post.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
                post.addHeader(HttpHeaders.AUTHORIZATION, oauthKey);
                post.setEntity(new StringEntity(data, ContentType.APPLICATION_JSON));
                try (CloseableHttpResponse response = httpClient.execute(post)) {
                    if (response.getStatusLine().getStatusCode() == 200) {
                        return true;
                    } else {
                        String responseString = IOUtils.toString(response.getEntity().getContent());
                        if (response.getStatusLine().getStatusCode() / 100 == 4) {
                            YandexEnvironmentType environmentType = YandexEnvironmentProvider.getEnvironmentType();
                            if (environmentType == YandexEnvironmentType.PRODUCTION) {
                                log.error("Solomon reported client error " + response.getStatusLine() + ". Response:\n" + responseString);
                            }

                            return false;
                        } else {
                            throw new RuntimeException("Unable to send metrics to Solomon: http code " + response.getStatusLine() +
                                    ". Response:\n" + responseString);
                        }
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException("Unable to send metrics to Solomon", e);
            }
        });
    }

    public boolean pushMetrics(List<SolomonSensor> sensors, SolomonCommonLabels commonLabels) {
        if (sensors.size() <= maxSensors) {
            return _pushMetrics(sensors, commonLabels);
        } else {
            for (List<SolomonSensor> part : Lists.partition(sensors, maxSensors)) {
                boolean success = _pushMetrics(part, commonLabels);
                if (!success) {
                    return false;
                }
            }
            return true;
        }
    }
}
