package com.xhkjedu.gateway.interceptors;

import com.alibaba.fastjson.JSON;
import com.xhkjedu.gateway.config.ConfigKey;
import com.xhkjedu.gateway.exceptions.GatewayException;
import com.xhkjedu.gateway.mapper.ModuleCheckMapper;
import com.xhkjedu.gateway.model.TModuleCheck;
import com.xhkjedu.gateway.utils.AES;
import com.xhkjedu.gateway.utils.ApiUtil;
import com.xhkjedu.gateway.utils.JedisUtil;
import com.xhkjedu.gateway.vo.SchoolVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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.codec.ServerCodecConfigurer;
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.CollectionUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

    private static final String AES_KEY = "XINGHUOLIAOYUAN7";

    @Resource
    private ModuleCheckMapper moduleCheckMapper;
    @Resource
    private ServerCodecConfigurer codecConfigurer;
    @Resource
    private JedisUtil jedisUtil;

    @PostConstruct
    public void init() {
        ServerCodecConfigurer.ServerDefaultCodecs serverDefaultCodecs = codecConfigurer.defaultCodecs();
        serverDefaultCodecs.maxInMemorySize(ConfigKey.paramsize);//设置限制50MB
    }

    @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();
        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, "验证失败，请刷新重试");
        }

        if(!isListNotEmpty(decrypts)){
            //兼容之前版本，验证st的值进行加解密
            List<String> oldDecrypts = headers.get("st");
            decrypt = isListNotEmpty(oldDecrypts)?oldDecrypts.get(0):"false";
        }

        Mono<Void> voidMono;
        if ("false".equals(decrypt)) {
            //白名单中的接口
            voidMono = returnMononew(chain, exchange);
        }else{
            voidMono = readBody(exchange, chain);
        }

        String userid = ApiUtil.listToStr(headers.get("Xh-User-Id"));
        //验证token
        String key = ApiUtil.listToStr(headers.get("Xh-Token-Key"));
        String token = ApiUtil.listToStr(headers.get("Xh-Token-Value"));
        if (!rtn) {//非白名单接口验证token
            String redis_token = jedisUtil.get(key);//系统缓存中保存的token

            if (ApiUtil.isEmpty(token) || !token.equals(redis_token)
                    || ApiUtil.isEmpty(key) || !key.split("_")[1].equals(userid)) {
                throw new GatewayException(2, "登录失效");
            } else {
                jedisUtil.refreshToken(key, redis_token);//刷新token
            }
        }

        if(ApiUtil.isNotEmpty(userid)){
            //验证操作用户模块权限
            SchoolVo schoolVo = moduleCheckMapper.getSchoolCloudByUserid(Integer.parseInt(userid));
            if(schoolVo.getUsertype() == 1 || schoolVo.getUsertype() == 0){
                String umodule_key = schoolVo.getCloudcode() + "_module_" + schoolVo.getSchoolid() + "_" + userid;
                String umodule_value = jedisUtil.get(umodule_key);//获取用户的模块权限
                List<String> userModules = null;//用户模块全新啊

                if(ApiUtil.isEmpty(umodule_value)){
                    //缓存中用户模块为空，获取用户角色对应所有模块
                    userModules = moduleCheckMapper.listModuleurlsByUserid(Integer.parseInt(userid));
                    if(ApiUtil.isListNotEmpty(userModules)){
                        //用户有模块权限，存入缓存
                        jedisUtil.set(umodule_key, userModules, ConfigKey.redisumtime);
                    }else{
                        throw new GatewayException(1, "没有权限");
                    }
                }else{
                    userModules = JSON.parseArray(umodule_value,String.class);
                }

                String allmodule_key = "modules_operate";//校验模块对应操作
                String allmodule_value = jedisUtil.get(allmodule_key);
                List<TModuleCheck> allModules = null;//校验模块对应操作
                if(ApiUtil.isEmpty(allmodule_value)){
                    allModules = moduleCheckMapper.listAllModuleChecks();
                    if(ApiUtil.isListNotEmpty(allModules)){
                        jedisUtil.set(allmodule_key,allModules,ConfigKey.redismoduletime);
                    }
                }else{
                    allModules = JSON.parseArray(allmodule_value,TModuleCheck.class);
                }

                if(ApiUtil.isListNotEmpty(allModules)){
                    //校验模块有值，进行校验。校验模块对应操作如果不在
                    Map<String,List<TModuleCheck>> mMap = allModules.stream().collect(Collectors.groupingBy(m -> m.getOperate()));
                    if(mMap.containsKey(path)){
                        List<TModuleCheck> checkList = mMap.get(path);
                        List<String> checkUrlList = checkList.stream().map(TModuleCheck::getModuleurl).collect(Collectors.toList());

                        boolean hasIntersection = userModules.stream().anyMatch(checkUrlList::contains);
                        if(!hasIntersection){
                            throw new GatewayException(1, "没有权限");
                        }
                    }
                }
            }
        }
        return voidMono;
    }

    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());
        ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders());
        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;
        }
    }
}
