package com.crunii.micro.gateway.filter.web;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.crunii.micro.common.constants.CommonConstants;
import com.crunii.micro.domain.dto.Worker;
import com.crunii.micro.gateway.GatewayConstants;
import com.crunii.micro.gateway.config.ReplayAttackFilterConfig;
import com.crunii.micro.gateway.utils.SymmetricCryptoUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
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.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ReplayAttackFilter implements GlobalFilter, Ordered {

    private AntPathMatcher pathMatcher = new AntPathMatcher();
    private static final String REPLAY_ATTACK_TOKEN = "mi";
    private static final String REPLAY_ATTACK_REDIS_KEY = "mi";
    private static final String MI_KEY_NONCE = "w";
    private static final String MI_KEY_TIME = "t";
    private static final String MI_KEY_REQUEST_URL = "r";
    private static final String MI_KEY_USER_ID = "u";
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ReplayAttackFilterConfig replayAttackFilterConfig;

    @SneakyThrows
    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, GatewayFilterChain chain) {
        if (!replayAttackFilterConfig.getEnable()) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        // 设置白名单
        List<String> whiteList = replayAttackFilterConfig.getWhiteList();
        if(!CollectionUtils.isEmpty(whiteList)){
            if(whiteList.contains(path)){
                return chain.filter(exchange);
            }
        }

//        if(!pathMatcher.match(CommonConstants.AUTH_PATTERN,path)){
//            return chain.filter(exchange);
//        }
        HttpHeaders headers = request.getHeaders();
        String mi = headers.getFirst(REPLAY_ATTACK_TOKEN);
        if (StrUtil.isNotBlank(mi)) {
            // 解密
            mi = SymmetricCryptoUtil.decryptFromString(mi);
            if (StrUtil.isBlank(mi)) {
                //mi解密异常
                log.error("重放攻击拦截，mi解密异常，请求地址：" + path);
                return resultHttpState(exchange,HttpStatus.FORBIDDEN);
            }
            // {"t":1678692621590,"w":"uuid","r":"/admin/xxx","u": 9999}
            JSONObject miObj = JSONUtil.parseObj(mi);
            String nonce = miObj.getStr(MI_KEY_NONCE);
            if (StrUtil.isBlank(nonce)) {
                //mi校验不通过
                log.info("重放攻击拦截，mi校验nonce不通过，请求地址：{}，nonce为空：{}", path, nonce);
                return resultHttpState(exchange,HttpStatus.FORBIDDEN);
            }
            if (!Validator.isUUID(nonce)) {
                //mi校验不通过
                log.info("重放攻击拦截，mi校验nonce不通过，请求地址：{}，nonce不为uuid：{}", path, nonce);
                return resultHttpState(exchange,HttpStatus.FORBIDDEN);
            }
            String redisKey = REPLAY_ATTACK_REDIS_KEY + StrPool.UNDERLINE + miObj.getStr(MI_KEY_NONCE);
            RMapCache<String, String> rMap = redissonClient.getMapCache(REPLAY_ATTACK_REDIS_KEY);
            if (!rMap.containsKey(redisKey)) {
                String clientIP = getClientIp(exchange);
                miObj.set("ip", clientIP);
                long nowTimestamp = Calendar.getInstance().getTimeInMillis();
                Long timestamp = miObj.getLong(MI_KEY_TIME);
                Worker worker = exchange.getAttribute(GatewayConstants.CURRENT_USER);
                if (Objects.nonNull(worker)) {
                    Integer uid = miObj.getInt(MI_KEY_USER_ID);
                    if (Objects.isNull(uid)) {
                        //mi校验不通过
                        log.info("重放攻击拦截，mi校验uid不通过，请求地址：{}，当前登录用户: {}，请求用户：{}", path, worker.getLoginId(), uid);
                        return resultHttpState(exchange,HttpStatus.UNAUTHORIZED);
                    }
                }
                if (Objects.isNull(timestamp)) {
                    //mi校验不通过
                    log.info("重放攻击拦截，mi校验timestamp不通过{}", path);
                    return resultHttpState(exchange,HttpStatus.FORBIDDEN);
                }
                String rUrl = miObj.getStr(MI_KEY_REQUEST_URL);
                if (StrUtil.isNotBlank(rUrl) && rUrl.contains(path)) {
                    rMap.put(redisKey, miObj.toString(), replayAttackFilterConfig.getReplayAttackRedisTimeOut(), TimeUnit.MINUTES);
                } else {
                    //mi校验不通过
                    log.info("重放攻击拦截，mi校验url不通过，请求地址：{}，参数地址：{}", path, rUrl);
                    return resultHttpState(exchange,HttpStatus.FORBIDDEN);
                }
            } else {
                //mi已存在在
                log.info("重放攻击拦截，mi已存在在，请求地址：{}，key：{}", path, redisKey);
                return resultHttpState(exchange,HttpStatus.FORBIDDEN);
            }
        } else {
            //没有mi
            log.info("重放攻击拦截，没有mi，请求地址：{}", path);
            return resultHttpState(exchange,HttpStatus.FORBIDDEN);
        }
        return chain.filter(exchange);
    }

    private String getClientIp(ServerWebExchange exchange) {
        List<String> list = exchange.getRequest().getHeaders().get("X-Forwarded-For");
        if (!CollectionUtils.isEmpty(list)) {
            return list.get(0);
        }
        return null;
    }

    private Mono<Void> resultHttpState(ServerWebExchange exchange,HttpStatus status) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(status);
        DataBuffer wrap = response.bufferFactory().wrap(status.getReasonPhrase().getBytes());
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return -99; // 在 TokenAuthenticationFilter 后执行
    }

}

