package com.kxs.gateway.api.filter;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.kxs.common.core.constant.enums.ErrorTypeEnum;
import com.kxs.common.core.exception.AesDecodeException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
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.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 请求解密处理
 *
 * @author 没秃顶的码农
 * @date 2023/11/17
 */
@Slf4j
@RequiredArgsConstructor
public class RequestDecoderFilter extends AbstractGatewayFilterFactory<Object> {

    private final AES cryptoAes;

    private static final String PARAM_NAME = "value";

    /**
     * 前端所传分页名称
     */
    private static final String PAGE_NAME = "pageNum";
    private static final String SIZE_NAME = "pageSize";

    private static final List<String> FILE_TYPES = List.of("multipart/form-data");

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            MediaType contentType = request.getHeaders().getContentType();

            if (contentType != null && FILE_TYPES.stream().anyMatch(contentType.toString()::startsWith)) {
                // 这是一个文件请求
                return chain.filter(exchange);
            }
            // 请求方法
            HttpMethod method = request.getMethod();
            if (method == HttpMethod.GET) {
                return decryptGetRequestParam(exchange, chain);
            }

            if (method == HttpMethod.POST || method == HttpMethod.PUT ) {
                return decryptPostBodyParam(exchange, chain);
            }
            if (method == HttpMethod.DELETE) {
                return decryptDeleteRequestParam(exchange, chain);
            }

            return chain.filter(exchange);

        };

    }

    /**
     * 解密删除请求参数
     *
     * @param exchange 交换
     * @param chain    链
     * @return {@link Mono}<{@link Void}>
     */
    private Mono<Void> decryptDeleteRequestParam(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        URI uri = exchange.getRequest().getURI();
        String path = uri.getPath();
        try{
            String[] pathSegments = path.split("/");
            String param = pathSegments[pathSegments.length - 1];
            byte[] decode = Base64.getMimeDecoder().decode(param);
            String decryptStr = cryptoAes.decryptStr(new String(decode));

            Pattern pattern = Pattern.compile("(.+/)[^/]+$");
            Matcher matcher = pattern.matcher(path);

            if (matcher.find()) {
                String lastSegment = matcher.group(1); // 包括最后一个/
                path = lastSegment + decryptStr;
                request = exchange.getRequest().mutate().uri(URI.create(path)).build();
                //缓存请求参数
                exchange.getAttributes().put("requestBody", decryptStr);
            }
        }catch (Exception e) {
            log.error("解密异常，请求地址：{}", path);
            return Mono.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
        }

        return chain.filter(exchange.mutate().request(request.mutate().build()).build());
    }

    /**
     * 解密post-body参数
     *
     * @param exchange exchange
     * @param chain    chain
     * @return mono＜void＞
     */
    private Mono<Void> decryptPostBodyParam(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求体,修改请求体
        ServerRequest serverRequest = ServerRequest.create(exchange,
                HandlerStrategies.withDefaults().messageReaders());

        //获取请求头
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        Flux<DataBuffer> modifiedBody = serverRequest.bodyToFlux(String.class).flatMap(body -> {
            // 解密请求体
            try {
//                log.info("待解密请求参数：{}", body);
                byte[] decode = Base64.getMimeDecoder().decode(body);
                String decryptStr = cryptoAes.decryptStr(new String(decode));
//				log.info("解密后参数：{}", decryptStr);

                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
                DataBuffer bodyDataBuffer = dataBufferFactory.wrap(decryptStr.getBytes());
                //添加到请求体
                exchange.getAttributes().put("requestBody", decryptStr);
                return Flux.just(bodyDataBuffer);
            } catch (Exception e) {
                log.error(ErrorTypeEnum.DECRYPT_ERROR.getDescription(), e);
                return Flux.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
            }
        });
        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                exchange.getRequest()) {
            @NotNull
            @Override
            public HttpHeaders getHeaders() {
                //重新设置请求头
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_TYPE);
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                assert contentType != null;
                httpHeaders.set(HttpHeaders.CONTENT_TYPE, contentType.toString());

                return httpHeaders;
            }

            @NotNull
            @Override
            public Flux<DataBuffer> getBody() {
                return modifiedBody;
            }
        };
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }


    /**
     * 解密GET请求参数
     *
     * @param exchange exchange
     * @param chain    chain
     * @return mono＜void＞
     */
    private Mono<Void> decryptGetRequestParam(ServerWebExchange exchange, GatewayFilterChain chain) {

        try {
            MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
            String decryptedParam = queryParams.getFirst(PARAM_NAME);
            if (CharSequenceUtil.isEmpty(decryptedParam)) {
                return chain.filter(exchange);
            }
            byte[] decode = Base64.getMimeDecoder().decode(decryptedParam);
//			log.info("GET:{},请求待解密数据 :{}", exchange.getRequest().getURI(),decryptedParam);
            String decryptStr = cryptoAes.decryptStr(new String(decode));
//			log.info("GET解密后数据 :{}", decryptStr);

            JSONObject decryptJson = JSON.parseObject(decryptStr);
            //构建新的请求参数webflux
            MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(decryptJson.size());
            decryptJson.forEach((key, value) -> {
                //转换分页参数，前后端定义不一致的问题
                if (PAGE_NAME.equals(key)) {
                    key = "current";
                }
                if (SIZE_NAME.equals(key)) {
                    key = "size";
                }
                if (value instanceof JSONArray) {
                    JSONArray values = JSON.parseArray(value.toString());
                    String finalKey = key;
                    values.forEach(v -> parameters.add(finalKey, URLEncoder.encode(String.valueOf(v), StandardCharsets.UTF_8)));
                } else {
                    //过滤空字符串转为null
                    if(StrUtil.isNotEmpty(String.valueOf(value))){
                        parameters.add(key, URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8));
                    }
                }
            });
            // 构建修改后的URI
            URI modifiedUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
                    .replaceQueryParams(parameters)
                    .build(true)
                    .toUri();
            // 修改请求，设置新的URI
            ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
                    .uri(modifiedUri)
                    .build();
            // 用修改后的请求继续处理请求链
            exchange.getAttributes().put("requestBody", parameters);
            return chain.filter(exchange.mutate().request(modifiedRequest).build());
        } catch (Exception e) {
            log.error(ErrorTypeEnum.DECRYPT_ERROR.getDescription(), e);
            return Mono.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
        }

    }

}
