package com.elab.spring.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import com.elab.core.utils.ObjectUtils;
import com.elab.core.utils.StringUtils;
import com.elab.log.utils.CatMsgConstants;
import com.elab.spring.callback.IRestFallback;
import com.elab.spring.exception.rest.CoolingException;
import com.elab.spring.factory.HttpComponentsClientRestfulHttpRequestFactory;
import com.elab.spring.factory.HttpsClientRequestFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;

/**
 * http调用工具
 *
 * @author Liukx
 * @create 2018-04-26 14:45
 * @email liukx@elab-plus.com
 **/
public class RestTemplateUtils {


    private Logger logger = LoggerFactory.getLogger(RestTemplateUtils.class);
    public RestTemplate restTemplate;

    public RestTemplate getRestTemplate;

    private HttpHeaders headers;

    private IRestFallback restFallback;
    /**
     * 连接时长
     */
    private Integer connectTimeout = 1000;
    /**
     * 读取时长
     */
    private Integer readTimeout = 5000;

    private RestTemplate getRestTemplate() {
        return restTemplate;
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public HttpHeaders getHeaders() {
        return headers;
    }

    public void setHeaders(HttpHeaders headers) {
        this.headers = headers;
    }

    public Integer getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(Integer connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public Integer getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(Integer readTimeout) {
        this.readTimeout = readTimeout;
    }

    public RestTemplateUtils() {
        System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
        //复杂构造函数的使用
        SimpleClientHttpRequestFactory requestFactory = new HttpsClientRequestFactory();
        // 设置超时
        requestFactory.setConnectTimeout(getConnectTimeout());
        // 读取超时
        requestFactory.setReadTimeout(getReadTimeout());

//        restTemplate = new RestTemplate(requestFactory);
        restTemplate = new RestTemplate(requestFactory);
//         替换String的默认字符集
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));

        //get请求适配body的请求工厂,内部采用httpclient
        HttpComponentsClientRestfulHttpRequestFactory getRequestFactory = new HttpComponentsClientRestfulHttpRequestFactory();
        // 设置超时
        requestFactory.setConnectTimeout(getConnectTimeout());
        // 读取超时
        requestFactory.setReadTimeout(getReadTimeout());

        //利用复杂构造器可以实现超时设置，内部实际实现为 HttpClient
        getRestTemplate = new RestTemplate(getRequestFactory);
        //         替换String的默认字符集
        getRestTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));


        //设置HTTP请求头信息，实现编码等
        headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
    }

    public void setRestFallback(IRestFallback restFallback) {
        this.restFallback = restFallback;
    }

    /**
     * post数据放送
     *
     * @param url
     * @param reqParam
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T post(String url, Object reqParam, Class<T> clazz) {
        return post(url, headers, reqParam, clazz);
    }

    /**
     * 执行post请求
     *
     * @param url          url
     * @param reqParam     请求参数
     * @param clazz        返回类型
     * @param restFallback 失败回调
     * @param <T>
     * @return
     */
    public <T> T post(String url, Object reqParam, Class<T> clazz, IRestFallback restFallback) {
        return post(url, headers, reqParam, clazz, restFallback);
    }

    /**
     * 执行post请求
     *
     * @param url         url
     * @param httpHeaders head参数
     * @param reqParam    请求参数
     * @param clazz       类
     * @param <T>
     * @return
     */
    public <T> T post(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz) {
        return post(url, httpHeaders, reqParam, clazz, this.restFallback);
    }

    /**
     * post请求最终发送
     *
     * @param url          路径
     * @param httpHeaders  请求头
     * @param reqParam     请求参数
     * @param clazz        结果返回对象
     * @param restFallback 失败回调方法
     * @param <T>
     * @return
     */
    public <T> T post(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz, IRestFallback<?> restFallback) {
        String newUrl = getUrl(url);
        Transaction t = Cat.getProducer().newTransaction(CatMsgConstants.THIRD_PARTY, newUrl);
        logger.debug(" URL : " + url);

        if (httpHeaders != null) {
            logger.debug(" hreader : " + JSON.toJSONString(httpHeaders));
        }

        if (reqParam != null) {
            logger.debug(" RequestData : " + StringUtils.logOut(logData(reqParam)));
        }
        T responseData = null;
        try {
            //利用容器实现数据封装，发送
            HttpEntity<Object> entity = new HttpEntity<Object>(reqParam, httpHeaders);
            requestBefore(url, httpHeaders, reqParam, clazz, restFallback);
            responseData = restTemplate.postForObject(url, entity, clazz);
            t.setStatus(Transaction.SUCCESS);
            logResponse(responseData);
            requestAfter(url, httpHeaders, reqParam, clazz, restFallback);
        } catch (Exception e) {
            e.printStackTrace();
            if (!(e instanceof CoolingException)) {
                logger.warn("第三方调用异常:", e);
            }
            t.setStatus(e.getClass().getSimpleName());
            if (restFallback != null) {
                logger.debug("触发异常回调 : " + restFallback.toString());
                return (T) restFallback.post(url, httpHeaders, reqParam, clazz, e);
            }
        } finally {
            t.complete();
        }
        return responseData;
    }

    /**
     * delete 删除请求
     *
     * @param url
     * @param httpHeaders
     * @param reqParam
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T delete(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz) {
        return exchange(url, HttpMethod.DELETE, reqParam, clazz, httpHeaders, null);
    }

    /**
     * 修改请求
     *
     * @param url
     * @param httpHeaders
     * @param reqParam
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T put(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz) {
        return exchange(url, HttpMethod.PUT, reqParam, clazz, httpHeaders, null);
    }

    private <T> void requestAfter(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz, IRestFallback<?> restFallback) throws Exception {
        if (restFallback != null) {
            restFallback.requestAfter(url, httpHeaders, reqParam, clazz);
        }
    }

    private <T> void requestBefore(String url, HttpHeaders httpHeaders, Object reqParam, Class<T> clazz, IRestFallback<?> restFallback) throws Exception {
        if (restFallback != null) {
            restFallback.requestBefore(url, httpHeaders, reqParam, clazz);
        }
    }

    /**
     * 通用的返回日志
     *
     * @param responseData
     * @param <T>
     */
    private <T> void logResponse(T responseData) {
        if (dontCareType(responseData)) {
            logger.debug(" 不关心的类型 ... ");
        } else {
            logger.debug(" ResponseData : " + StringUtils.logOut(logData(responseData)));
        }
    }

    /**
     * get请求发送
     *
     * @param url
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T get(String url, Class<T> clazz) {
        return get(url, clazz, this.restFallback);
    }

    public <T> T get(String url, Class<T> clazz, IRestFallback restFallback) {
        String newUrl = getUrl(url);
        Transaction t = Cat.getProducer().newTransaction(CatMsgConstants.THIRD_PARTY, newUrl);
        logger.debug(" URL : " + url);
        //利用容器实现数据封装，发送
        T responseData = null;
        try {
            responseData = restTemplate.getForObject(url, clazz);
            t.setStatus(Transaction.SUCCESS);
            if (responseData != null) {
                logResponse((T) responseData);
            }
        } catch (Exception e) {
            if (!(e instanceof CoolingException)) {
                logger.warn("第三方调用异常:", e);
            }
            t.setStatus(e.getClass().getSimpleName());
            if (restFallback != null) {
                logger.debug("触发失败回调 : " + restFallback.toString());
                return (T) restFallback.get(url, clazz, e);
            }
        } finally {
            t.complete();
        }
        return responseData;
    }

    public <T> T get(String url, Map<String, String> httpHeaders, Class<T> clazz) {
        HttpHeaders headerMap = new HttpHeaders();
        httpHeaders.forEach((K, V) -> {
            headerMap.put(K, Arrays.asList(V));
        });
        return get(url, null, clazz, headerMap, this.restFallback);
    }

    /**
     * get方法请求
     *
     * @param url
     * @param clazz
     * @param httpHeaders
     * @param restFallback
     * @param <T>
     * @return
     */
    public <T> T get(String url, Class<T> clazz, HttpHeaders httpHeaders, IRestFallback restFallback) {
        return get(url, null, clazz, httpHeaders, restFallback);
    }

    /**
     * get请求最终执行的方法
     *
     * @param url          URL
     * @param body         请求体
     * @param clazz        转换的结果类对象型
     * @param httpHeaders  header
     * @param restFallback 失败回调的类
     * @param <T>
     * @return
     */
    public <T> T get(String url, Object body, Class<T> clazz, HttpHeaders httpHeaders, IRestFallback restFallback) {
        return exchange(url, HttpMethod.GET, body, clazz, httpHeaders, restFallback);
    }

    private <T> T exchange(String url, HttpMethod httpMethod, Object body, Class<T> clazz, HttpHeaders httpHeaders, IRestFallback restFallback) {
        String newUrl = getUrl(url);
        Transaction t = Cat.getProducer().newTransaction(CatMsgConstants.THIRD_PARTY, newUrl);
        logger.debug(" URL : " + url);
        if (httpHeaders != null) {
            logger.debug(" header : " + JSON.toJSONString(httpHeaders));
        }
        //利用容器实现数据封装，发送
        T responseData = null;
        try {
            HttpEntity<Object> entity = new HttpEntity<Object>(body, httpHeaders);
            ResponseEntity<T> result = null;
            if (body == null) {
                result = restTemplate.exchange(url, httpMethod, entity, clazz);
            } else {
                logger.debug(" DATA :" + body);
                result = getRestTemplate.exchange(url, httpMethod, entity, clazz);
            }
            responseData = result.getBody();
            t.setStatus(Transaction.SUCCESS);
            if (responseData != null) {
                logResponse((T) responseData);
            }
        } catch (Exception e) {
            if (!(e instanceof CoolingException)) {
                logger.warn("第三方调用异常:", e);
            }
            t.setStatus(e.getClass().getSimpleName());
            if (restFallback != null) {
                logger.debug("触发失败回调 : " + restFallback.toString());
                return (T) restFallback.get(url, clazz, e);
            }
        } finally {
            t.complete();
        }
        return responseData;
    }

    private <T> boolean dontCareType(T responseData) {
        return responseData instanceof byte[] || responseData instanceof InputStream;
    }

    /**
     * 文件上传接口
     *
     * @param url
     * @param reqParam
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T postUpload(String url, Object reqParam, Class<T> clazz) {
        logger.debug(" URL : " + url);
        logger.debug(" RequestData : " + ObjectUtils.objectParseJsonStr(reqParam));

        String newUrl = getUrl(url);
        Transaction t = Cat.getProducer().newTransaction(CatMsgConstants.THIRD_PARTY, newUrl);
        T responseData = null;
        try {
            //利用容器实现数据封装，发送
            HttpEntity<Object> entity = new HttpEntity<Object>(reqParam, new HttpHeaders());
            ResponseEntity<T> exchange = restTemplate.exchange(url, HttpMethod.POST, entity, clazz);
            responseData = exchange.getBody();
            if (exchange != null && responseData != null) {
                logger.debug(" ResponseData : " + StringUtils.logOut(logData(responseData)));
            }
            t.setStatus(Transaction.SUCCESS);
        } catch (Exception e) {
            logger.warn("------ 第三方接口调用失败 : ", e);
            t.setStatus(e.getClass().getSimpleName());
        } finally {
            t.complete();
        }
        return responseData;
    }

    /**
     * 获取除开?号后面的URL
     *
     * @param url
     * @return
     */
    private String getUrl(String url) {
        if (ObjectUtils.isNotEmpty(url)) {
            String newUrl = url;
            if (url.indexOf("?") > 0) {
                newUrl = url.substring(0, url.indexOf("?"));
            }
            return newUrl;
        }
        return url;
    }

    private String logData(Object data) {
        if (data == null) {
            return "";
        }

        if (data instanceof String) {
            return data.toString();
        }
        String logData = "";
        try {
            logData = ObjectUtils.objectParseJsonStr(data);
        } catch (Exception e) {
            return data.toString();
        }
        return logData;
    }

    public static void main(String[] args) {
        String param = "{\n" +
                "\t\"pageno\":\"1\",\n" +
                "\t\"pagesize\":\"10\",\n" +
                "\t\"houseid\":\"102\",\n" +
                "\t\"time\":\"2017-04-13\"\n" +
                "}";
        JSONObject jsonObject = JSON.parseObject(param);
        RestTemplateUtils restTemplateUtils = new RestTemplateUtils();
//        JSONModel post = restTemplateUtils.post("http://localhost:9005/skyforest/dd/pageList", jsonObject, JSONModel.class);
//        System.out.println(JSON.toJSONString(post));
    }

}
