package com.tofly.toflygateway.filter;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.tofly.common.core.constant.SecurityConstants;
import com.tofly.common.core.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

/**
 *  请求加密解密
 */
@Slf4j
@Component
public class ReadAndChangeResFilter implements GlobalFilter, Ordered {

    private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n";
    private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";

    private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
    private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";

    private final static String TOKEN_USERINFO_CACHE_FLAG = "TI-%s";

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String X_AUTHORIZATION_HEADER = "X-Authorization";
    private static final String BEARER_PREFIX = "Bearer ";

    private static final String SIGN_HEAD = "X-Sign";
    private static final String DECRYPTED_PARAM = "decryptedParam";
    public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/auth/login," +
            "/yt/user/reset," +
            "/v2/api-docs," +
            "/file/loadImg," +
            "/file/download," +
            "/file/downloadFile," +
            "/file/loadAudio," +
            "/yt/log/addlog";

    private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1. 清洗请求头中from 参数
        ServerHttpRequest request = exchange.getRequest().mutate()
                .headers(httpHeaders -> httpHeaders.remove(SecurityConstants.FROM))
                .build();
        // 2. 重写StripPrefix
        addOriginalRequestUrl(exchange, request.getURI());
        String rawPath = request.getURI().getRawPath();
        String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
                .skip(1L).collect(Collectors.joining("/"));
        ServerHttpRequest newRequest = request.mutate()
                .path(newPath)
                .build();
        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());

        return dealRequest(newPath, newRequest, exchange, chain);
    }

    private Mono<Void> dealRequest(String newPath, ServerHttpRequest newRequest, ServerWebExchange exchange, GatewayFilterChain chain) {
        Mono<Void> mono = chain.filter(exchange.mutate()
                .request(newRequest.mutate().build())
                .build());
        boolean sign = newRequest.getHeaders().getFirst(SIGN_HEAD) != null;
        if (!sign) {
            return mono;
        }

        //加密处理，判断路径是否包含特定的加密字符串，如果包含直接解密然后调用接口
        String newPath1 = getPathVariable(newRequest);
        if (StringUtil.isNotEmpty(newPath1)) {
            newRequest = newRequest.mutate()
                    .path(newPath1)
                    .build();
            newPath = newPath1;
        }
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        if (newRequest.getMethod() == HttpMethod.GET) {
            contentType = null;
        }

        // 非get请求
        if (Objects.nonNull(contentType)) {
            if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                // json 请求体处理
                mono = this.transferBody(newPath, exchange, chain);
            } else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
                // multipart/form-data处理
                mono = this.fileRequest(newPath, contentType, exchange, chain);
            } else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
                // x-www-form-urlencoded 格式处理
                mono = this.xwFromBody(newPath, exchange, chain);
            }
        } else {
            mono = getRequest(newRequest, exchange, chain);
        }

        return mono;
    }

    private String getPathVariable(ServerHttpRequest newRequest) {
        String resultNewPath = null;
        int minSm2Length = 180;
        String newPath = newRequest.getPath().toString();
        String[] listItem = newPath.split("/");
        List<String> result = new ArrayList<>();
        boolean flag = false;
        for (String item : listItem) {

            if (item.length() < minSm2Length) {
                result.add(item);
            } else {
                flag = true;
                String decryptItem = SM2Utils.decrypt(item, SM2Config.privateKey);
                result.add(decryptItem);
            }
        }
        if (flag) {
            resultNewPath = StringUtil.join(result, "/");
        }
        return resultNewPath;
    }

    private Mono<Void> getRequest(ServerHttpRequest newRequest, ServerWebExchange exchange, GatewayFilterChain chain) {
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
        log.warn("request GET {} :{}", DECRYPTED_PARAM, queryParams.toString());
        String decryptedParam = queryParams.getFirst(DECRYPTED_PARAM);
        if (decryptedParam != null) {
            String encodedDecryptedParam = SM2decryptParams(decryptedParam);
            // 编码参数值
            //String encodedDecryptedParam =getParamURLEncoder(decryptedValue);
            // 构建修改后的URI
            URI modifiedUri = UriComponentsBuilder.fromUri(newRequest.getURI())
                    // .replaceQueryParam("param", encodedDecryptedParam)
                    .replaceQuery(encodedDecryptedParam)
                    .build(true)
                    .toUri();
            // 修改请求，设置新的URI
            newRequest = exchange.getRequest().mutate()
                    .uri(modifiedUri)
                    .build();
        }
        return chain.filter(exchange.mutate()
                .request(newRequest.mutate()
                        .build()).build());

    }

    private String getDecryptedParam(String str) {
        String result = str;
        String[] list = str.split("&");
        for (String item : list) {
            if (item.contains(DECRYPTED_PARAM.concat("="))) {
                result = item.split("=")[1];
                break;
            }
        }

        return result;
    }

    private String getParamURLEncoder(String str) {
        try {
            List<String> list = new ArrayList<>();

            String[] listSplit = str.split("&");
            for (String item : listSplit) {
                if (item.contains("=")) {
                    if (item.split("=").length == 2) {
                        item = URLEncoder.encode(item.split("=")[0], "UTF-8").concat("=")
                                .concat(URLEncoder.encode(item.split("=")[1], "UTF-8"));
                    }
                }
                list.add(item);
            }
            return StringUtil.join(list, "&");
        } catch (Exception e) {
            return str;
        }

    }

    //修改form参数
    private Mono<Void> fileRequest(String newPath, MediaType contentType, ServerWebExchange exchange, GatewayFilterChain chain) {
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    String oldBody = addPara(contentType.toString(), new String(bytes));
                    // byte[] bytes2 = oldBody.getBytes();
                    byte[] bytes1 = oldBody.getBytes();
                    byte[] bytes2 = byteMerger(Arrays.copyOf(bytes, bytes.length - 4), bytes1);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes2);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });

                    ServerHttpRequestDecorator decorator = newDecorator(exchange, bytes2.length, cachedFlux);

                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(decorator);
                    mutatedRequest = mutatedRequest.mutate()
                            .path(newPath)
                            .build();
                    ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                    return ServerRequest.create(mutatedExchange, MESSAGE_READERS)
                            .bodyToMono(byte[].class)
                            .then(chain.filter(mutatedExchange));
                });
    }

    public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
        byte[] bt3 = new byte[bt1.length + bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }

    //修改 x-www-form-urlencoded参数
    private void changeParamByXwForm(Flux<DataBuffer> body, DataBufferHolder holder, MyCachedBodyOutputMessage outputMessage) {
        try {
            AtomicBoolean flag = new AtomicBoolean(false);
            body.subscribe(dataBuffer -> {
                int len = dataBuffer.readableByteCount();
                flag.set(true);
                holder.length = len;
                byte[] bytes = new byte[len];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                String oldBody = new String(bytes, StandardCharsets.UTF_8);
                oldBody = getDecryptedParam(oldBody);
                String newBody = SM2decryptParams(oldBody);
                DataBuffer data = outputMessage.bufferFactory().allocateBuffer();
                data.write(newBody.getBytes(StandardCharsets.UTF_8));
                holder.length = data.readableByteCount();
                holder.dataBuffer = data;
            });
            if (!flag.get()) {
                //没有参数的时候，接口会报错，这里手动添加一个随机数作为参数
                String newBody = "&v=" + new Random(10);
                DataBuffer data = outputMessage.bufferFactory().allocateBuffer();
                data.write(newBody.getBytes(StandardCharsets.UTF_8));
                holder.length = data.readableByteCount();
                holder.dataBuffer = data;
            }
        } catch (Exception e) {
            log.error("ReadAndChangeResFilter.changeParamByXwForm() Error ===================> ", e);
        }
    }

    @Override
    public int getOrder() {
        return -100;
    }

    /**
     * 修改 x-www-form-urlencoded参数
     */
    private Mono<Void> xwFromBody(String newPath, ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(Mono::just);
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
        ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
        requestBuilder.headers(k -> k.remove("Content-length"));

        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    Flux<DataBuffer> body = outputMessage.getBody();
                    DataBufferHolder holder = new DataBufferHolder();
                    changeParamByXwForm(body, holder, outputMessage);
                    ServerHttpRequestDecorator decorator = newDecorator(exchange, holder.length, Flux.just(holder.dataBuffer));
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(decorator);
                    mutatedRequest = mutatedRequest.mutate()
                            .path(newPath)
                            .build();
                    return chain.filter(exchange.mutate().request(mutatedRequest).build());
                }));
    }


    /**
     * 私钥解密参数
     */
    private String SM2decryptParams(String params) {

        try {
            String decryptParams = SM2Utils.decrypt(params, SM2Config.privateKey);
            if (StringUtil.isNotBlank(decryptParams)) {
                return getParamURLEncoder(decryptParams);
            } else {
                return params;
            }

        } catch (Exception e) {
            return params;
        }
    }

    /**
     * 修改修改body参数
     */
    private Mono<Void> transferBody(String newPath, ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(Mono::just);
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
        ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();
        requestBuilder.headers(k -> k.remove("Content-length"));

        Mono mono = bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    //解决body内数据过长读取不完整的问题
                    Flux<DataBuffer> body = outputMessage.getBody();
                    DataBufferHolder holder = new DataBufferHolder();
                    try {
                        body.subscribe(dataBuffer -> {
                            int len = dataBuffer.readableByteCount();
                            holder.length = len;
                            byte[] bytes = new byte[len];
                            dataBuffer.read(bytes);
                            DataBufferUtils.release(dataBuffer);
                            String oldBody = new String(bytes, StandardCharsets.UTF_8);
                            String decryptBody = SM2decryptParams(oldBody);
                            JsonNode jsonNode = readNode(decryptBody);
                            // checkTokenAndSaveUser((ObjectNode)jsonNode);
                            DataBuffer data = outputMessage.bufferFactory().allocateBuffer();
                            data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
                            holder.length = data.readableByteCount();
                            holder.dataBuffer = data;
                        });

                    } catch (Exception e) {
                    }


                    ServerHttpRequestDecorator decorator = newDecorator(exchange, holder.length, Flux.just(holder.dataBuffer));
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(decorator);
                    mutatedRequest = mutatedRequest.mutate()
                            .path(newPath)
                            .build();
                    return chain.filter(exchange.mutate().request(mutatedRequest).build());

                }));

        return mono;
    }


    private Mono<Void> handleFailedRequest(ServerWebExchange exchange, String message) {
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        exchange.getResponse().setStatusCode(HttpStatus.OK);
        return exchange.getResponse().writeWith(Flux.just(buffer));
    }

    private JsonNode readNode(String in) {
        try {
            return objectMapper.readTree(in);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private class DataBufferHolder {
        DataBuffer dataBuffer;
        int length;
    }

    /**
     * 修改form参数
     *
     * @param contentType 请求类型
     * @param bodyString  请求body信息
     */
    @SneakyThrows
    public static String addPara(String contentType, String bodyString) {
        StringBuffer stringBuffer = new StringBuffer();

        String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9);//获取随机字符传信息
        String boundary_end = StrUtil.format("--{}--\r\n", boundary);
        Map<String, Object> formMap = Maps.newHashMap();
        /**
         *
         * 根据自己需求进行对bodyString信息修改，例如下面，根据boundary对传入的bodyString进行了分割
         *  String[] split = bodyString.split(boundary);
         *  然后将修改完后的信息封装到formMap中，需要注意的是，file文件需要以new FileResource(file, fileName)的形式当作value放到formMap中
         */
        String part = "^\r\nContent-Disposition: form-data; name=\"([^/?]+)\"\r\n\r\n([^/?]+)\r\n--?$";
        Pattern r = Pattern.compile(part);
        String[] split = bodyString.split(boundary);
        for (int x = 0; x < split.length - 1; x++) {
            Matcher m = r.matcher(split[x]);
            if (m.find()) {
                String name = m.group(1);
                String value = m.group(2);
                if (name.equals(DECRYPTED_PARAM)) {
                    String result = SM2Utils.decrypt(value, SM2Config.privateKey);
                    String parmar[] = result.split("&");
                    for (String item : parmar) {
                        if (item.contains("=")) {
                            String[] ss = item.split("=");
                            if (ss.length == 1) {
                                formMap.put(item.split("=")[0].toString(), "");

                            } else {
                                formMap.put(item.split("=")[0].toString(), item.split("=")[1]);
                            }
                        }

                    }

                }
            }
        }
        Integer count = 0;
        for (Map.Entry<String, Object> entry : formMap.entrySet()) {
            stringBuffer.append(appendPart(boundary, entry.getKey(), entry.getValue(), count));
            count++;
        }
        stringBuffer.append(boundary_end);//拼接结束信息
        log.info(stringBuffer.toString());

        return stringBuffer.toString();
    }

    public ServerHttpRequestDecorator newDecorator(ServerWebExchange exchange, long dataLength, Flux<DataBuffer> body) {
        return new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                //数据长度变了以后 需要修改header里的数据，不然接收数据时会异常
                //我看别人说删除会自动补充数据长度，但我这个版本不太行
//				long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
//				if (contentLength > 0) {
                httpHeaders.setContentLength(dataLength);
//				} else {
//					httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
//				}
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return body;
            }
        };
    }


    /**
     * 添加Multipart表单的数据项
     *
     * @param boundary      随机串信息
     * @param formFieldName 表单名
     * @param value         值，可以是普通值、资源（如文件等）
     */
    private static String appendPart(String boundary, String formFieldName, Object value, Integer count) throws IORuntimeException {
        StringBuffer stringBuffer = new StringBuffer();
        // 多资源
        if (value instanceof MultiResource) {
            for (Resource subResource : (MultiResource) value) {
                appendPart(boundary, formFieldName, subResource, count);
            }
            return stringBuffer.toString();
        }

        if (count != 0) {
            stringBuffer.append("--").append(boundary).append(StrUtil.CRLF);
        } else {
            stringBuffer.append(StrUtil.CRLF);
//			stringBuffer.append(boundary).append(StrUtil.CRLF);
        }

        if (value instanceof Resource) {
            // 文件资源（二进制资源）
            final Resource resource = (Resource) value;
            final String fileName = resource.getName();
            stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName)));
            // 根据name的扩展名指定互联网媒体类型，默认二进制流数据
            stringBuffer.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream")));
        } else {
            // 普通数据
            stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)).append(value);
        }
        stringBuffer.append(StrUtil.CRLF);
        return stringBuffer.toString();
    }

}


