package ru.yandex.autotests.directapi.darkside.clients;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import ru.yandex.autotests.directapi.darkside.connection.DarksideConfig;
import ru.yandex.autotests.directapi.darkside.exceptions.DarkSideException;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.function.Supplier;

import static ru.yandex.autotests.direct.utils.clients.tvm.ServiceTicketProvider.SERVICE_TICKET_HEADER;

/**
 * User: omaz
 * Date: 16.08.13
 * Http клиент
 */
public class HttpClient extends BaseIntapiClient {
    protected URI serviceUrl;
    private CloseableHttpClient client;
    private final Supplier<String> tvmTicketProvider;

    public HttpClient(DarksideConfig config) {
        this(config, null);
    }

    public HttpClient(DarksideConfig config, Supplier<String> tvmTicketProvider) {
        super(config, "HTTP");
        this.tvmTicketProvider = tvmTicketProvider;
        serviceUrl = config.getHost();
    }

    @Override
    public void setConnectionTimeoutMillis(int connectionTimeoutMillis) {
        this.connectionTimeoutMillis = connectionTimeoutMillis;
        this.client = null;
    }

    protected CloseableHttpClient getClient() {
        if (client == null) {
            SSLContextBuilder builder = new SSLContextBuilder();
            try {
                //настройки, чтобы доверять self-signed ssl-сертификатам по умолчанию
                //(нужно для доступа по https к методам Управлятора)
                builder.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
                        new TrustSelfSignedStrategy() {
                            @Override
                            public boolean isTrusted(X509Certificate[] chain, String authType)
                                    throws CertificateException {
                                return true;
                            }
                        });

                SSLConnectionSocketFactory sslsf =
                        new SSLConnectionSocketFactory(builder.build(), new AllowAllHostnameVerifier());

                RequestConfig config = RequestConfig.custom()
                        .setConnectTimeout(connectionTimeoutMillis)
                        .setConnectionRequestTimeout(connectionTimeoutMillis)
                        .setSocketTimeout(connectionTimeoutMillis).build();

                client = HttpClients.custom()
                        .setSSLSocketFactory(sslsf)
                        .setDefaultRequestConfig(config)
                        .build();
            } catch (Exception e) {
                throw new DarkSideException("Не удалось инициализировать HTTP-клиент", e);
            }
        }
        return client;
    }

    public HttpResponse sendPostRequest(URI uri) {
        HttpPost httpPost = new HttpPost(uri);
        httpPost.releaseConnection();
        return sendRequest(httpPost);
    }

    public HttpResponse sendUrlencodedPostRequest(URI uri) {
        HttpPost httpPost = new HttpPost(uri);
        httpPost.releaseConnection();
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
        return sendRequest(httpPost);
    }

    public HttpResponse sendGetRequest(URI uri) {
        HttpGet httpGet = new HttpGet(uri);
        return sendRequest(httpGet);
    }

    public InputStream sendGetRequestWithResponseAsInputStream(URI uri) {
        HttpGet httpGet = new HttpGet(uri);
        return sendRequestWithResponseAsInputStream(httpGet);
    }

    protected HttpResponse sendRequest(HttpUriRequest request) {
        IntapiMessage message = new IntapiMessage();
        try {
            addTvmHeader(request);

            message.setUrl(request.getURI().toString());
            message.setRequest(request.toString());

            org.apache.http.HttpResponse response = getClient().execute(request);
            String respText = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
            message.setStatusCode(response.getStatusLine().toString());
            message.setResponse(respText);

            return new HttpResponse(response.getStatusLine().getStatusCode(), respText);
        } catch (OutOfMemoryError e) {
            message.setResponse("Слишком большой ответ метода, нужно переделать тест на построчный разбор " +
                    "с помощью метода sendRequestWithResponseAsInputStream");
            throw new DarkSideException(message.getResponse(), e);
        } catch (IOException e) {
            message.setResponse("Ошибка при вызове HTTP-метода: " + e.getMessage());
            throw new DarkSideException(message.getResponse(), e);
        } finally {
            attachIntapiMessage(message);
        }
    }

    private void addTvmHeader(HttpUriRequest request) {
        if (tvmTicketProvider != null && StringUtils.isNotEmpty(tvmTicketProvider.get())) {
            request.addHeader(SERVICE_TICKET_HEADER, tvmTicketProvider.get());
        }
    }

    /**
     * Отправка запроса с ответом в виде InputStream
     */
    protected InputStream sendRequestWithResponseAsInputStream(HttpUriRequest request) {
        IntapiMessage message = new IntapiMessage();
        try {
            addTvmHeader(request);

            message.setUrl(request.getURI().toString());
            message.setRequest(request.toString());

            org.apache.http.HttpResponse response = getClient().execute(request);
            message.setStatusCode(response.getStatusLine().toString());
            message.setResponse("Слишком большой ответ метода, поэтому будем построчно его разбирать");

            return response.getEntity().getContent();
        } catch (IOException e) {
            message.setResponse("Ошибка при вызове HTTP-метода: " + e.getMessage());
            throw new DarkSideException(message.getResponse(), e);
        } finally {
            attachIntapiMessage(message);
        }
    }

    public URI buildURIFromParams(String method, Map<String, String> params) {
        URIBuilder builder;
        builder = new URIBuilder(serviceUrl);
        builder.setPath(builder.getPath() + "/" + method);
        for (Map.Entry<String, String> entry : params.entrySet())
            builder.setParameter(entry.getKey(), entry.getValue());
        try {
            return builder.build();
        } catch (URISyntaxException e) {
            throw new DarkSideException("Ошибка формирования запроса", e);
        }
    }

    public URI buildURIFromParams(String method, String params) {
        URIBuilder builder;
        builder = new URIBuilder(serviceUrl);
        builder.setPath(builder.getPath() + "/" + method);
        builder.setCustomQuery(params);
        try {
            return builder.build();
        } catch (URISyntaxException e) {
            throw new DarkSideException("Ошибка формирования запроса", e);
        }
    }

}
