package com.xhkjedu.gateway.interceptors;

import com.xhkjedu.gateway.exceptions.GatewayException;
import com.xhkjedu.gateway.utils.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.*;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * @Description 请求参数解密
 * @Author WN
 * Date 2023/2/1 14:22
 **/
@Component
@Slf4j
public class DecryptRequestionBodyFilter extends JedisUtil implements GlobalFilter, Ordered {
    @Value("${Xh-St}")
    private String xhSt;
    @Value("${whitelistAPI}")
    private String whitelistAPI;

    private static String AES_KEY = "XINGHUOLIAOYUAN7";

    private AntPathMatcher antPathMatcher= new AntPathMatcher();

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

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath(); // 当前调用方法的url
        HttpHeaders headers = request.getHeaders();
//        log.info("HttpMethod:{},Url:{}", request.getMethod(), request.getURI().getRawPath());
        String st = "Xh-St";
        response.getHeaders().add(st, this.xhSt);
        response.getHeaders().add("Access-Control-Expose-Headers", st);
        List<String> decrypts = headers.get(st);
        String decrypt = isListNotEmpty(decrypts)?decrypts.get(0):"false";
        boolean rtn = ApiUtil.whiteAPI(path, whitelistAPI);
        if (!rtn && !xhSt.equals(decrypt)) {//非白名单接口验证加密方式
            throw new GatewayException(1, "验证失败，请刷新重试");
        }
        Mono<Void> voidMono;
        if (!decrypt.equals("true")) {
            voidMono = returnMononew(chain, exchange);
        }else{
            voidMono = readBody(exchange, chain);
        }

        String key = ApiUtil.listToStr(headers.get("Xh-Token-Key"));
        String token = ApiUtil.listToStr(headers.get("Xh-Token-Value"));
        if (!rtn) {//非白名单接口验证token
            String redis_token = get(key);//系统缓存中保存的token
            String userid = ApiUtil.listToStr(headers.get("Xh-User-Id"));
            if (ApiUtil.isEmpty(token) || !token.equals(redis_token)
                    || ApiUtil.isEmpty(key) || !key.split("_")[1].equals(userid)) {
                //GatewayException throwable = new GatewayException(2, "登录失效");
                //return Mono.error(throwable);
                throw new GatewayException(2, "登录失效");
            } else {
                refreshToken(key, redis_token);//刷新token
            }
        }
        return voidMono;

//        //  登录跳过网关验证，检查白名单（配置）最好把不拦截路径放入配置文件，此处通过正则
//        if(antPathMatcher.match("/**/api/login/auth/**",path)){
//            return readBody(exchange, chain);
//        }
//        // 处理参数
//        MediaType contentType = headers.getContentType();
//        long contentLength = headers.getContentLength();
//        if (contentLength > 0) {
//            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
//                return readBody(exchange, chain);
//            }
//        }
//        return chain.filter(exchange);
    }

    ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }


    private Mono<Void> returnMononew(GatewayFilterChain chain, ServerWebExchange exchange) {
        long startTime = System.currentTimeMillis();
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - startTime;
            if (executeTime > 1000) {
                String path = exchange.getRequest().getURI().getPath();
                log.error(" {}；耗时：{}s", path, executeTime / 1000);
            }
        }));
    }
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        //重新构造request，参考ModifyRequestBodyGatewayFilterFactory
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        //重点
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            //因为约定了终端传参的格式，所以只考虑json的情况，如果是表单传参，请自行增加
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
                String newBody = null;
                try{
                    // 解密body
                    newBody = AES.decrypt(body, AES_KEY);
                }catch (Exception e){
                    e.getMessage();
                    log.error("解密参数出错："+e.getMessage());
                }
                return Mono.just(newBody);
            }
            return Mono.empty();
        });
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        //猜测这个就是之前报400错误的元凶，之前修改了body但是没有重新写content length
        headers.remove("Content-Length");
        //MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage，只不过CachedBodyOutputMessage不是公共的
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
            return returnMononew(chain, exchange.mutate().request(decorator).build());
        }));
    }


    @Override
    public int getOrder() {
        return 0;
    }

    private boolean isListNotEmpty(List list) {
        if (CollectionUtils.isEmpty(list)){
            return false;
        } else {
            return true;
        }
    }
}
