package org.matrix.actuators.httpclient;

import com.alibaba.fastjson.JSONObject;
import io.netty.handler.codec.http.HttpHeaderValues;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.matrix.actuators.Actuator;
import org.matrix.actuators.util.CompleteExpressionUtil;
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.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.config.HttpRequestConfig;
import org.matrix.database.entity.Environment;
import org.matrix.database.service.IEnvironmentService;
import org.matrix.exception.GlobalException;
import org.matrix.exception.HttpRequestException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * HttpClient调用封装
 * @author HuangXiahao
 **/
@Component
public class HttpClientActuator implements Actuator {

    /**
     * 正则表达式，匹配 URL 是否符合HTTP协议
     */
    private static final String PATTERN_URL = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";

    final
    HttpRequestConfig httpRequestConfig;

    final
    CompleteExpressionUtil completeExpressionUtil;

    final
    IEnvironmentService environmentService;

    public HttpClientActuator(CompleteExpressionUtil completeExpressionUtil, IEnvironmentService environmentService, HttpRequestConfig httpRequestConfig) {
        this.completeExpressionUtil = completeExpressionUtil;
        this.environmentService = environmentService;
        this.httpRequestConfig = httpRequestConfig;
    }

    /**
     * 初始化请求内数据并补全动态变量
     */
    private void completeHttpRequestDetail(HttpRequestDetail httpRequestDetail,Long envId,Long projectId){
        for (RequestHeader header : httpRequestDetail.getHeaders()) {
            //获取动态变量，并将动态变量替换进去
            header.setKey(
                    completeExpressionUtil.completeDynamicVariable(
                            header.getKey(),
                            envId,
                            projectId)
            );
            header.setValue(
                    completeExpressionUtil.completeDynamicVariable(
                            header.getValue(),
                            envId,
                            projectId)
            );
        }
        for (RequestBody requestBody : httpRequestDetail.getRequestBodies()) {
            requestBody.setKey(
                    completeExpressionUtil.completeDynamicVariable(
                            requestBody.getKey(),
                            envId,
                            projectId)
            );
            requestBody.setValue(
                    completeExpressionUtil.completeDynamicVariable(
                            requestBody.getValue(),
                            envId,
                            projectId)
            );
        }
        if (!StringUtils.isEmpty(httpRequestDetail.getStringValue())){
            httpRequestDetail.setStringValue(
                    completeExpressionUtil.completeDynamicVariable(
                            httpRequestDetail.getStringValue(),
                            envId,
                            projectId)
            );
        }
        httpRequestDetail.setUrl(completeExpressionUtil.completeDynamicVariable(
                httpRequestDetail.getUrl(),
                envId,
                projectId));
        boolean urlMatch = Pattern.matches(PATTERN_URL, httpRequestDetail.getUrl());
        if (!urlMatch){
            Environment environment = environmentService.getById(envId);
            if (environment==null){
                if (envId==null||envId==-1L){
                    throw new GlobalException("[HTTP执行器] 未传入执行环境" );
                }else {
                    throw new GlobalException("[HTTP执行器] 执行环境不存在，环境ID： "+envId);
                }
            }
            httpRequestDetail.setUrl(environment.getIp()+httpRequestDetail.getUrl());
        }
    }

    /**
     * 发起Http请求
     *
     */
    public HttpResponseDetail sendHttpRequest(HttpRequestDetail httpRequestDetail,Long envId,Long projectId) {
        completeHttpRequestDetail(httpRequestDetail,envId,projectId);
        CloseableHttpResponse response = null;
        Long  startTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        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 = httpRequestConfig.getClient().execute(requestBase);
            Long endTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
            return new HttpResponseDetail(
                    url,
                    StringUtils.isEmpty(httpRequestDetail.getStringValue())? JSONObject.toJSONString(httpRequestDetail.getRequestBodies()) :httpRequestDetail.getStringValue(),
                    response,
                    EntityUtils.toString(response.getEntity(), "UTF-8"),
                    HttpStatus.valueOf(response.getStatusLine().getStatusCode()),
                    endTime - startTime
            );
        }catch (HttpHostConnectException e){
            Long endTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
            return new HttpResponseDetail(
                    url,
                    StringUtils.isEmpty(httpRequestDetail.getStringValue())? JSONObject.toJSONString(httpRequestDetail.getRequestBodies()) :httpRequestDetail.getStringValue(),
                    response,
                    String.format("目标主机拒绝连接,本次请求URL: %s ",httpRequestDetail.getUrl()),
                    HttpStatus.SERVICE_UNAVAILABLE,
                    endTime - startTime
            );
        } 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();
                }
            }else if(httpRequestDetail.getRequestType() == HttpRequestType.PATH){
                List<RequestBody> requestBodies = httpRequestDetail.getRequestBodies();
                Map<String, String> requestMap =
                        requestBodies.stream().collect(Collectors.toMap(RequestBody::getKey, RequestBody::getValue));
                url = completeExpressionUtil.completePathVariable(url,requestMap);
            }
        } 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("关闭返回流失败");
            }
        }
    }

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




}
