package ink.wgink.common.rpc.rest.request;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.common.rpc.rest.pojo.RemoteFileInputStream;
import ink.wgink.common.rpc.rest.pojo.RemoteRequest;
import ink.wgink.exceptions.FileException;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.rpc.RemoteRequestException;
import ink.wgink.exceptions.rpc.RemoteResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.*;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: RestRemoteRequest
 * @Description: Rest远程调用请求
 * @Author: WangGeng
 * @Date: 2021/9/18 21:00
 * @Version: 1.0
 **/
public class RestRemoteRequest {

    private static final Logger LOG = LoggerFactory.getLogger(RestRemoteRequest.class);
    private RestTemplate httpTemplate;
    private RestTemplate httpsTemplate;

    public RestRemoteRequest() {
        HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpComponentsClientHttpRequestFactory.setConnectTimeout(20 * 1000);
        httpComponentsClientHttpRequestFactory.setReadTimeout(60 * 1000);
        this.httpTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);

        HttpsClientRequestFactory httpsClientRequestFactory = new HttpsClientRequestFactory();
        httpsClientRequestFactory.setConnectTimeout(20 * 1000);
        httpsClientRequestFactory.setReadTimeout(60 * 1000);
        this.httpsTemplate = new RestTemplate(httpsClientRequestFactory);
    }

    public Object request(RemoteRequest remoteRequest) {
        RequestMethod requestMethod = remoteRequest.getRequestMethod();
        String remoteUri = remoteRequest.getRemoteUri();
        Map<String, String> headers = remoteRequest.getHeaders();
        MultiValueMap<String, Object> formVariableParams = remoteRequest.getFormVariableParams();
        Map<String, File> fileVariableParams = remoteRequest.getFileVariableParams();
        Map<String, RemoteFileInputStream> fileInputVariableParams = remoteRequest.getFileInputStreamVariableParams();
        Object jsonBody = remoteRequest.getFormVariableParams();
        Type resultType = remoteRequest.getResultType();
        String result;
        if (RequestMethod.GET.equals(requestMethod)) {
            result = get(remoteUri, headers);
        } else if (RequestMethod.DELETE.equals(requestMethod)) {
            result = delete(remoteUri, headers);
        } else if (RequestMethod.POST.equals(requestMethod)) {
            // form表单优先
            if (!fileVariableParams.isEmpty() || !fileInputVariableParams.isEmpty() || !formVariableParams.isEmpty()) {
                result = post(remoteUri, headers, fileVariableParams, fileInputVariableParams, formVariableParams);
            } else {
                result = post(remoteUri, headers, jsonBody);
            }
        } else if (RequestMethod.PUT.equals(requestMethod)) {
            result = put(remoteUri, headers, jsonBody);
        } else {
            throw new ParamsException("请求方法不支持");
        }
        // 除了集合其余均按对象
        return JSONObject.parseObject(result, resultType);
    }

    /**
     * GET请求
     *
     * @param remoteUri
     * @param headers
     * @return
     */
    public String get(String remoteUri, Map<String, String> headers) {
        try {
            HttpEntity<String> httpEntity = getHttpEntity(headers, null, null, null, null);
            ResponseEntity<String> responseEntity = getRestTemplate(remoteUri).exchange(remoteUri, HttpMethod.GET, httpEntity, String.class);
            return getStringResponse(responseEntity);
        } catch (Exception e) {
            return getErrorResponse(e);
        }
    }

    /**
     * DELETE请求
     *
     * @param remoteUri
     * @param headers
     * @return
     */
    public String delete(String remoteUri, Map<String, String> headers) {
        try {
            HttpEntity<String> httpEntity = getHttpEntity(headers, null, null, null, null);
            ResponseEntity<String> responseEntity = getRestTemplate(remoteUri).exchange(remoteUri, HttpMethod.DELETE, httpEntity, String.class);
            return getStringResponse(responseEntity);
        } catch (Exception e) {
            return getErrorResponse(e);
        }
    }

    /**
     * POST请求
     *
     * @param remoteUri
     * @param headers
     * @return
     */
    public String post(String remoteUri, Map<String, String> headers, Object jsonBody) {
        LOG.debug("Request POST Json: {}", jsonBody);
        try {
            HttpEntity<String> httpEntity = getHttpEntity(headers, null, null, null, jsonBody);
            ResponseEntity<String> responseEntity = getRestTemplate(remoteUri).exchange(remoteUri, HttpMethod.POST, httpEntity, String.class);
            return getStringResponse(responseEntity);
        } catch (Exception e) {
            return getErrorResponse(e);
        }
    }

    /**
     * POST请求
     *
     * @param remoteUri
     * @param headers
     * @return
     */
    public String post(String remoteUri,
                       Map<String, String> headers,
                       Map<String, File> fileMap,
                       Map<String, RemoteFileInputStream> fileInputStreamMap,
                       MultiValueMap<String, Object> formVariableParams) {
        LOG.debug("Request POST Form: {}", formVariableParams);
        try {
            HttpEntity<String> httpEntity = getHttpEntity(headers, fileMap, fileInputStreamMap, formVariableParams, null);
            ResponseEntity<String> responseEntity = getRestTemplate(remoteUri).exchange(remoteUri, HttpMethod.POST, httpEntity, String.class);
            return getStringResponse(responseEntity);
        } catch (Exception e) {
            return getErrorResponse(e);
        }
    }

    /**
     * PUT请求
     *
     * @param remoteUri
     * @param headers
     * @return
     */
    public String put(String remoteUri, Map<String, String> headers, Object jsonBody) {
        try {
            HttpEntity<String> httpEntity = getHttpEntity(headers, null, null, null, jsonBody);
            ResponseEntity<String> responseEntity = getRestTemplate(remoteUri).exchange(remoteUri, HttpMethod.PUT, httpEntity, String.class);
            return getStringResponse(responseEntity);
        } catch (Exception e) {
            return getErrorResponse(e);
        }
    }

    /**
     * 请求实体
     *
     * @param headerMap
     * @param jsonBody
     * @return
     */
    private HttpEntity<String> getHttpEntity(Map<String, String> headerMap,
                                             Map<String, File> fileMap,
                                             Map<String, RemoteFileInputStream> fileInputStreamMap,
                                             MultiValueMap<String, Object> formVariableParams,
                                             Object jsonBody) {
        LOG.debug("Request Header: {}", headerMap);
        HttpEntity<String> httpEntity;
        if (jsonBody != null) {
            HttpHeaders httpHeaders = getHttpHeaders(headerMap);
            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            if (jsonBody instanceof Collection) {
                httpEntity = new HttpEntity(JSONArray.toJSONString(jsonBody), httpHeaders);
            } else {
                httpEntity = new HttpEntity(JSONObject.toJSONString(jsonBody), httpHeaders);
            }
        } else if (!fileMap.isEmpty() || !fileInputStreamMap.isEmpty() || !formVariableParams.isEmpty()) {
            HttpHeaders httpHeaders = getHttpHeaders(headerMap);
            MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
            if (!fileMap.isEmpty()) {
                httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
                setFileVariable(multiValueMap, fileMap);
            } else if (!fileInputStreamMap.isEmpty()) {
                httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
                setFileInputStreamVariable(multiValueMap, fileInputStreamMap);
            } else {
                httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            }
            if (!formVariableParams.isEmpty()) {
                formVariableParams.forEach(multiValueMap::add);
            }
            httpEntity = new HttpEntity(multiValueMap, httpHeaders);
        } else {
            httpEntity = new HttpEntity(getHttpHeaders(headerMap));
        }
        return httpEntity;
    }

    /**
     * 设置文件
     *
     * @param multiValueMap
     * @param fileMap
     */
    private void setFileVariable(MultiValueMap<String, Object> multiValueMap, Map<String, File> fileMap) {
        try {
            for (Map.Entry<String, File> kv : fileMap.entrySet()) {
                String key = kv.getKey();
                File file = kv.getValue();
                FileInputStream fileInputStream = new FileInputStream(file);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                // 1M
                byte[] buf = new byte[1024 * 1000];
                for (int len = 0; (len = fileInputStream.read(buf)) > -1; ) {
                    byteArrayOutputStream.write(buf, 0, len);
                }
                ByteArrayResource fileAsResource = new ByteArrayResource(byteArrayOutputStream.toByteArray()) {
                    @Override
                    public String getFilename() {
                        return file.getName();
                    }

                    @Override
                    public long contentLength() {
                        return file.length();
                    }
                };
                multiValueMap.add(key, fileAsResource);
            }
        } catch (Exception e) {
            throw new FileException(e);
        }
    }

    /**
     * 设置文件
     *
     * @param multiValueMap
     * @param fileInputStreamMap
     */
    private void setFileInputStreamVariable(MultiValueMap<String, Object> multiValueMap, Map<String, RemoteFileInputStream> fileInputStreamMap) {
        try {
            for (Map.Entry<String, RemoteFileInputStream> kv : fileInputStreamMap.entrySet()) {
                String key = kv.getKey();
                RemoteFileInputStream remoteFileInputStream = kv.getValue();
                InputStreamResource inputStreamResource = new InputStreamResource(remoteFileInputStream.getInputStream()) {
                    @Override
                    public String getFilename() {
                        return remoteFileInputStream.getFileName();
                    }

                    @Override
                    public long contentLength() {
                        return remoteFileInputStream.getFileSize();
                    }
                };
                multiValueMap.add(key, inputStreamResource);
            }
        } catch (Exception e) {
            throw new FileException(e);
        }
    }

    /**
     * 得到HTTP请求头
     *
     * @param headersMap
     * @return
     */
    private HttpHeaders getHttpHeaders(Map<String, String> headersMap) {
        HttpHeaders httpHeaders = new HttpHeaders();
        // 接收JSON格式返回
        httpHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        for (Map.Entry<String, String> kv : headersMap.entrySet()) {
            httpHeaders.add(kv.getKey(), kv.getValue());
        }
        return httpHeaders;
    }

    /**
     * 得到响应结果
     *
     * @param responseEntity
     * @return
     */
    private String getStringResponse(ResponseEntity<String> responseEntity) {
        if (responseEntity.getStatusCode() != HttpStatus.OK &&
                responseEntity.getStatusCode() != HttpStatus.NO_CONTENT) {
            throw new RemoteResponseException("远程调用响应状态码不支持: " + responseEntity.getStatusCode());
        }
        String responseBody = responseEntity.getBody();
        LOG.debug("Response body: {}", responseBody);
        return responseBody;
    }


    /**
     * 失败响应
     *
     * @param e
     * @return
     */
    public String getErrorResponse(Exception e) {
        if (e instanceof HttpClientErrorException) {
            HttpClientErrorException exception = (HttpClientErrorException) e;
            String errorBody = exception.getResponseBodyAsString();
            LOG.debug("Error body: {}", errorBody);
            JSONObject errorJSONBody = JSON.parseObject(errorBody);
            if (errorJSONBody.containsKey("code") && errorJSONBody.containsKey("detail") && errorJSONBody.containsKey("msg")) {
                throw new RemoteResponseException(errorJSONBody.getString("msg"));
            }
            throw new RemoteResponseException(errorBody);
        }
        throw new RemoteRequestException(e.getMessage(), e);
    }

    /**
     * 获取resttemplate模板
     *
     * @param url
     * @return
     */
    private RestTemplate getRestTemplate(String url) {
        if (StringUtils.startsWithIgnoreCase(url, "http://")) {
            return this.httpTemplate;
        } else if (StringUtils.startsWithIgnoreCase(url, "https://")) {
            return this.httpsTemplate;
        }
        throw new RemoteRequestException("schema error, only support http(s)");
    }

}
