package com.sifang.modules.sys.util.http;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.sifang.modules.sys.util.ListUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.*;

public class EzTemplateBase extends RestTemplate {

    protected final RestTemplate restTemplate;

    public EzTemplateBase(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public EzTemplateBase() {
        this.restTemplate = new RestTemplate(getClientHttpRequestFactory());
    }

    protected static RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
        ObjectMapper lax = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MappingJackson2HttpMessageConverter c = new MappingJackson2HttpMessageConverter();
        c.setObjectMapper(lax);
        RestTemplate restTemplate = new RestTemplate(factory);
        Iterator<HttpMessageConverter<?>> iterator = restTemplate.getMessageConverters().iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter<?> next = iterator.next();
            if (next instanceof MappingJackson2HttpMessageConverter) {
                iterator.remove();
                break;
            }
        }
        restTemplate.getMessageConverters().add(c);
        return restTemplate;
    }

    public <T> T postForm(String url, Map<String, Object> data, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {

        long start = System.currentTimeMillis();
        MultiValueMap<String, Object> postData = new LinkedMultiValueMap<>();
        if (data != null) {
            Set<Map.Entry<String, Object>> entries = data.entrySet();
            for (Map.Entry<String, Object> e : entries) {
                Object val = e.getValue();
                if (val != null) {
                    if (ListUtils.isArray(val) || (val instanceof List) || val instanceof Set) {
                        List<String> list = ListUtils.extractList(val);
                        for (String el : list) {
                            postData.add(e.getKey(), el);
                        }
                    } else {
                        postData.set(e.getKey(), val.toString());
                    }
                }
            }
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(postData, headers);
        T body = doPostForm(url, responseType, entity, uriVariables);
        logger.info(String.format("post to %s consumed: %s", url, (System.currentTimeMillis() - start)));
        return body;
    }

    public <T> T postForm(String url, Map<String, Object> data, Class<T> responseType, Object... uriVariables) throws RestClientException {
        long start = System.currentTimeMillis();
        MultiValueMap<String, Object> postData = new LinkedMultiValueMap<>();
        if (data != null) {
            postData.setAll(data);
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(postData, headers);
        T ret = restTemplate.postForObject(url, entity, responseType, uriVariables);
        logger.info(String.format("post to %s consumed: %s", url, (System.currentTimeMillis() - start)));
        return ret;
    }

    private <T> T doPostForm(String url, ParameterizedTypeReference<T> responseType, HttpEntity<MultiValueMap<String, Object>> entity, Object[] uriVariables) {
        return restTemplate.exchange(url, HttpMethod.POST, entity, responseType, uriVariables).getBody();
    }

    public <T> T postJson(String url, Map<String, Object> data, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        return postJson(url, new ValueHolder<Object>(data), responseType, uriVariables);
    }

    public <T> T postJson(String url, Object data, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        return postJson(url, new ValueHolder<>(data), responseType, uriVariables);
    }

    public <T> T postJson(String url, Object data, Class<T> responseType, Object... uriVariables) throws RestClientException {
        return postJson(url, new ValueHolder<>(data), responseType, uriVariables);
    }

    private <T, K> T postJson(String url, ValueHolder<K> data, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        long start = System.currentTimeMillis();
        HttpHeaders headers = setJsonHttpHeaders();
        HttpEntity<K> entity = new HttpEntity<>(data.getData(), headers);
        T body = doPostJson(url, responseType, entity, uriVariables);
        logger.info(String.format("post to %s consumed: %s", url, (System.currentTimeMillis() - start)));
        return body;
    }

    private <T, K> T postJson(String url, ValueHolder<K> data, Class<T> responseType, Object... uriVariables) throws RestClientException {
        long start = System.currentTimeMillis();
        HttpHeaders headers = setJsonHttpHeaders();
        HttpEntity<K> entity = new HttpEntity<>(data.getData(), headers);
        T body = doPostJson(url, responseType, entity, uriVariables);
        logger.info(String.format("post to %s consumed: %s", url, (System.currentTimeMillis() - start)));
        return body;
    }

    private HttpHeaders setJsonHttpHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8"));
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        return headers;
    }

    protected <T, K> T doPostJson(String url, ParameterizedTypeReference<T> responseType, HttpEntity<K> entity, Object[] uriVariables) {
        HttpEntity<String> data = new HttpEntity<>(new Gson().toJson(entity.getBody()), entity.getHeaders());
        return restTemplate.exchange(url, HttpMethod.POST, data, responseType, uriVariables).getBody();
    }

    protected <T, K> T doPostJson(String url, Class<T> responseType, HttpEntity<K> entity, Object[] uriVariables) {
        HttpEntity<String> data = new HttpEntity<>(new Gson().toJson(entity.getBody()), entity.getHeaders());
        return restTemplate.exchange(url, HttpMethod.POST, data, responseType, uriVariables).getBody();
    }

    static class ValueHolder<T> {
        private final T data;

        ValueHolder(T data) {
            this.data = data;
        }

        public T getData() {
            return data;
        }
    }

    protected static ClientHttpRequestFactory getClientHttpRequestFactory() {
        int timeout = 30000;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout).build();

        CloseableHttpClient client = HttpClientBuilder.create()
                .setDefaultRequestConfig(config)
                .setMaxConnPerRoute(1000)
                .setMaxConnTotal(1000)
                .build();

        return new HttpComponentsClientHttpRequestFactory(client);
    }
}
