package org.matrix.actuators.httpclient;

import io.netty.handler.codec.http.HttpHeaderValues;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.HttpHostConnectException;
import org.matrix.actuators.Actuator;
import org.matrix.actuators.util.CompleteExpressionUtil;
import org.matrix.config.HttpRequestConfig;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.matrix.exception.HttpRequestException;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * HttpClient调用封装
 * todo 打印LOG
 * @author HuangXiahao
 **/
public class HttpClientActuator implements Actuator {

    HttpRequestConfig config;

    CookieStore cookieStore;

    CloseableHttpClient client;

    private final Long projectId;

    private final Long envId;


    public HttpClientActuator(HttpRequestConfig config, Long env, Long projectId) {
        this.config = config;
        this.cookieStore = config.cookieStore();
        try {
            this.client = config.client();
        } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
        this.projectId = projectId;
        this.envId = env;
    }

    public CloseableHttpClient getClient() {
        return client;
    }



    /**
     * 初始化请求内数据并补全动态变量
     */
    private void completeHttpRequestDetail(HttpRequestDetail httpRequestDetail){
        for (RequestHeader header : httpRequestDetail.getHeaders()) {
            //todo 李迪凡 将header中的key和value里的 动态变量补全
            //获取动态变量，并将动态变量替换进去
            header.setName(
                    CompleteExpressionUtil.completeDynamicVariable(
                            header.getName(),
                            envId,
                            projectId)
            );
            header.setValue(
                    CompleteExpressionUtil.completeDynamicVariable(
                            header.getValue(),
                            envId,
                            projectId)
            );
        }
        for (RequestBody requestBody : httpRequestDetail.getRequestBodies()) {
            //todo 李迪凡 将requestBody中的key和value里的 动态变量补全
            requestBody.setKey(
                    CompleteExpressionUtil.completeDynamicVariable(
                            requestBody.getKey(),
                            envId,
                            projectId)
            );
            requestBody.setValue(
                    CompleteExpressionUtil.completeDynamicVariable(
                            requestBody.getValue(),
                            envId,
                            projectId)
            );
        }
        if (!StringUtils.isEmpty(httpRequestDetail.getStringValue())){
            //todo 李迪凡 将httpRequestDetail的stringValue  动态变量补全
            httpRequestDetail.setStringValue(
                    CompleteExpressionUtil.completeDynamicVariable(
                            httpRequestDetail.getStringValue(),
                            envId,
                            projectId)
            );
        }
        //todo 李迪凡 将httpRequestDetail的url  动态变量补全
        httpRequestDetail.setUrl(CompleteExpressionUtil.completeDynamicVariable(
                httpRequestDetail.getUrl(),
                envId,
                projectId));
    }

    /**
     * 发起Http请求
     *
     */
    public HttpResponseDetail sendHttpRequest(HttpRequestDetail httpRequestDetail) {
        completeHttpRequestDetail(httpRequestDetail);
        CloseableHttpResponse response = null;
        Date startTime = new Date();
        String url = initUrlString(httpRequestDetail);
        HttpRequestBase requestBase = initHttpRequestBase(url, httpRequestDetail.getMethod());
        if (requestBase instanceof HttpEntityEnclosingRequestBase) {
            HttpEntity httpRequestBase = initHttpEntity(httpRequestDetail);
            ((HttpEntityEnclosingRequestBase) requestBase).setEntity(httpRequestBase);
        }
        requestBase.setHeaders(httpRequestDetail.getHeadersArray());
        try {
            response = getClient().execute(requestBase);
            Date endTime = new Date();
            return new HttpResponseDetail(
                    response,
                    EntityUtils.toString(response.getEntity(), "UTF-8"),
                    HttpStatus.valueOf(response.getStatusLine().getStatusCode()),
                    endTime.getTime() - startTime.getTime()
            );
        }catch (HttpHostConnectException e){
            Date endTime = new Date();
            return new HttpResponseDetail(
                    response,
                    String.format("目标主机拒绝连接,本次请求URL: %s ",httpRequestDetail.getUrl()),
                    null,
                    endTime.getTime() - startTime.getTime()
            );
        } catch (IOException e) {
            throw new HttpRequestException(String.format("解析返回值失败,本次请求详细参数如下: %s ",httpRequestDetail));
        } finally {
            //关闭请求request
            closeRequest(requestBase);
            //关闭response
            closeResponse(response);
        }
    }

    /**
     * 初始化请求HttpEntity
     *
     */
    public HttpEntity initHttpEntity(HttpRequestDetail httpRequestDetail) {
        HttpEntity httpEntity ;
        switch (httpRequestDetail.getRequestType()) {
            case FORM_DATA:
                httpEntity = getMultipartEntity(httpRequestDetail.getRequestBodies());
                break;
            case X_WWW_FORM_URLENCODED:
                httpEntity = getUrlEncodeFormEntity(httpRequestDetail.getRequestBodies());
                break;
            case JSON:
                httpEntity =  getStringEntity(httpRequestDetail.getStringValue());
                httpRequestDetail.addHeaders(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON.toString());
                break;
            case XML:
                httpEntity = getStringEntity(httpRequestDetail.getStringValue());
                httpRequestDetail.addHeaders(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_XML.toString());
                break;
            case TEXT:
                httpEntity = getStringEntity(httpRequestDetail.getStringValue());
                break;
            case BINARY:
                httpEntity = getByteArrayRequestEntity(httpRequestDetail.getStringValue());
                break;
            default:
                httpEntity = getEmptyStringEntity();
        }
        return httpEntity;
    }

    public HttpEntity getMultipartEntity(List<RequestBody> requestBodies) {
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
        for (RequestBody requestBody : requestBodies) {
            switch (requestBody.getType()) {
                case TEXT:
                    multipartEntityBuilder.addTextBody(requestBody.getKey(), requestBody.getValue(), ContentType.create("text/plain", Consts.UTF_8));
                    break;
                case FILE:
                    multipartEntityBuilder.addBinaryBody(requestBody.getKey(), getFileByPath(requestBody.getValue()));
                    break;
                default:
            }
        }
        return multipartEntityBuilder.build();
    }

    public HttpEntity getUrlEncodeFormEntity(List<RequestBody> requestBodies) {
        List<NameValuePair> param = new ArrayList<>();
        for (RequestBody requestBody : requestBodies) {
            param.add(new BasicNameValuePair(requestBody.getKey(), requestBody.getValue()));
        }
        return new UrlEncodedFormEntity(param, StandardCharsets.UTF_8);
    }


    public StringEntity getEmptyStringEntity() {
        return new StringEntity("", StandardCharsets.UTF_8);
    }

    public StringEntity getStringEntity(String value) {
        return new StringEntity(value, StandardCharsets.UTF_8);
    }


    public File getFileByPath(String path) {
        return new File(path);
    }

    public HttpEntity getByteArrayRequestEntity(String path) {
        try {
            File file = getFileByPath(path);
            byte[] bytesArray = new byte[(int) file.length()];
            FileInputStream fis = new FileInputStream(file);
            fis.read(bytesArray);
            fis.close();
            return new ByteArrayEntity(bytesArray);
        } catch (IOException ioException) {
            ioException.printStackTrace();
            return getEmptyStringEntity();
        }


    }

    public String initUrlString(HttpRequestDetail httpRequestDetail) {
        String url = httpRequestDetail.getUrl();
        try {
            if (httpRequestDetail.getRequestType() == HttpRequestType.QUERY) {
                if (httpRequestDetail.getMethod().equals(HttpMethod.GET)) {
                    URIBuilder uriBuilder;
                    uriBuilder = new URIBuilder(url);
                    for (RequestBody requestBody : httpRequestDetail.getRequestBodies()) {
                        switch (requestBody.getType()) {
                            case TEXT:
                                uriBuilder.setParameter(requestBody.getKey(), requestBody.getValue());
                                break;
                            case FILE:
                                throw new NullPointerException("QUERY请求不能传入文件流");
                            default:
                        }
                    }
                    url = uriBuilder.build().toString();
                }
            }
        } catch (URISyntaxException e) {
            throw new HttpRequestException(String.format("URL格式不正确，不正确的URL为： %s",url));
        }
        return url;
    }

    /**
     * 初始化HttpRequestBase
     *
     */
    public HttpRequestBase initHttpRequestBase(String url, HttpMethod httpMethod) {
        HttpRequestBase requestBase;
        switch (httpMethod) {
            case GET:
                requestBase = new HttpGet(url);
                break;
            case POST:
                requestBase = new HttpPost(url);
                break;
            case PUT:
                requestBase = new HttpPut(url);
                break;
            case DELETE:
                requestBase = new HttpDelete(url);
                break;
            case PATCH:
                requestBase = new HttpPatch(url);
                break;
            default:
                throw new IllegalStateException("不支持该类型的HTTP请求: " + httpMethod);
        }
        return requestBase;
    }

    /**
     * 关闭response（注意：用完记得关，不然会一直占用系统资源）
     *
     */
    public void closeResponse(CloseableHttpResponse response) {
        if (response != null) {
            if (response.getEntity() != null) {
                EntityUtils.consumeQuietly(response.getEntity());
            }
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw new HttpRequestException(String.format("关闭返回流失败"));
            }
        }
    }

    /**
     * 关闭requestBase（注意：用完记得关，不然会一直占用系统资源）
     *
     */
    public void closeRequest(HttpRequestBase requestBase) {
        requestBase.abort();
        requestBase.releaseConnection();
    }


}
