package com.heyu.api.gateway.filter;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.heyu.api.gateway.config.properties.XssProperties;
import com.heyu.api.gateway.util.ApiConstants;
import com.heyu.api.gateway.util.Log4Constans;
import com.heyu.api.gateway.util.OrderUtils;
import com.heyu.api.gateway.util.UserAuthContains;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * <p>
 * </p>*日志配置
 * @author wulin
 * @since 2023-08-07
 *
 * https://blog.csdn.net/qq_26274037/article/details/127800559
 */
@Configuration
@Slf4j
public class AuthWebFilter implements WebFilter {
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST = "127.0.0.1";
    private static final String SEPARATOR = ",";

    //由nginx配置透传过来
    private static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
    private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
    private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";


    @Autowired
    XssProperties xssProperties;




    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        long startTime = System.currentTimeMillis();
        ServerHttpRequest request = exchange.getRequest();
        String traceId = OrderUtils.getUserPoolOrder();
        String requestId = request.getId();//请求id
        MDC.put(Log4Constans.TRACE_ID, traceId);
        String url = request.getPath().toString();
        URI URIAll = request.getURI();
        String uri = URIAll.getPath();
        if("/favicon.ico".equals(uri)){
            ServerHttpResponse response = exchange.getResponse();
            DataBuffer bodyDataBuffer = response.bufferFactory().wrap("".getBytes());
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(bodyDataBuffer));
        }

        HttpHeaders httpHeaders = request.getHeaders();
        List<String> paths = httpHeaders.get(ApiConstants.X_FORWARDED_PATH);
        String xForwardedPath = null;
        if (!CollectionUtils.isEmpty(paths) && uri.equals("/")) {
            String path = paths.get(0);
            xForwardedPath = path;
        }

        String realIpAddress = getRealIpAddress(request);
        String m = request.getMethod().toString();
        log.info("AuthWebFilter api start time:{} ip:{} method:{} url:{},uri:{} param:{} headers:{},xForwardedPath:{}",
                startTime,
                realIpAddress,
                m,
                request.getPath(),
                uri,
                request.getQueryParams(),
                httpHeaders,
                xForwardedPath
               );

        if(xssProperties.getEnabled()) {
            if (xssProperties.getAuthIP() != null && xssProperties.getAuthIP().contains(realIpAddress)) {
                log.info("白名单IP，免验签");
                return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
            }
            if (xssProperties.getExcludeUrls().contains(url)) {
                log.info("排除免签url内，直接通过");
                return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
            }
            if (xssProperties.getAuthUrls().size() > 0) {
                for (String aurl : xssProperties.getAuthUrls()) {
                    if (url.contains(aurl)) {
                        log.info("免登陆url，免验签");
                        return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);

                    }
                }
            }
            String api_token = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TOKEN);
            String api_type = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TYPE);
            // String key = RedisConstans.IOT_TOKEN.concat(api_token);
            // return reactiveRedisTemplate.getExpire(key).map(Duration::getSeconds).flatMap(ttl -> {
            //     if (ttl == -1) {
            //         // 用户没登陆
            //         return Mono.error(new RuntimeException("未登录"));
            //     } else if (ttl <= 3600) {
            //         // token 将要失效
            //         return reactiveRedisTemplate.expire(key, Duration.ofDays(7)).then(chain.filter(exchange));
            //     } else {
            //         // 正常登录
            //         return chain.filter(exchange);
            //     }
            // });
            if (ObjectUtils.isEmpty(api_token) || ObjectUtils.isEmpty(api_type)) {
                log.info("未传输token和type，需要登录");
                return Mono.error(new RuntimeException("未登录"));
            }
            return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
        }
        log.info("未开启xss验签认证");

        return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
    }


    private Mono<Void> continueRequest(ServerWebExchange exchange, WebFilterChain chain, String customerIp, String requestId,
                                       long startTime,String traceId,String xForwardedPath){
        ServerWebExchange.Builder ex = exchange.mutate();
        ex.response(getResponse(exchange, requestId,traceId));
        ex.request(getRequest(exchange, customerIp, requestId,traceId,xForwardedPath));

        return chain.filter(ex.build()).contextWrite(context -> {
            Context contextTmp = context.put(Log4Constans.TRACE_ID, traceId);
            return contextTmp;
        }).doFinally(single ->{
            long endTime = System.currentTimeMillis();
            log.info("api end time:{}, total time:{}", endTime, endTime - startTime);
            MDC.remove(LogMdcConfiguration.PRINT_LOG_ID);
        });
    }

    private ServerHttpResponse getResponse(ServerWebExchange exchange, String requestId,String traceId){
        ServerHttpResponse response = exchange.getResponse();
        return new ServerHttpResponseDecorator(response){
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body){
                MDC.put(Log4Constans.TRACE_ID, traceId);
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        MDC.put(Log4Constans.TRACE_ID, traceId);
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer joinBuffer = dataBufferFactory.join(dataBuffers);
                        byte[] returnContent = new byte[joinBuffer.readableByteCount()];
                        joinBuffer.read(returnContent);
                        DataBufferUtils.release(joinBuffer);
                        String returnStr = new String(returnContent, StandardCharsets.UTF_8);
                        log.info("response:{}", returnStr);
                        return response.bufferFactory().wrap(returnContent);
                    }));
                }else if(body instanceof  Mono){
                    Mono<DataBuffer> monoBody = Mono.from(body);
                    return super.writeWith(monoBody.map(dataBuffer -> {
                        MDC.put(Log4Constans.TRACE_ID, traceId);
                        log.info("response:{}", dataBuffer.toString(StandardCharsets.UTF_8));
                        return response.bufferFactory().wrap(dataBuffer.toString(StandardCharsets.UTF_8).getBytes());
                    }));
                }
                return super.writeWith(body);
            }
        };
    }


    private ServerHttpRequest getRequest(ServerWebExchange exchange, String customerIp, String requestId, String traceId, String xForwardedPath) {
        ServerHttpRequest request = exchange.getRequest();
        if (StringUtils.isNotEmpty(xForwardedPath)) {
            String originXForwardedPath = xForwardedPath;
            if (xForwardedPath.contains(ApiConstants.API_INTERFACE)) {
                xForwardedPath = "/" + ApiConstants.API_INTERFACE + xForwardedPath.split(ApiConstants.API_INTERFACE)[1];
            }
            log.info("getRequest xForwardedPath is not null originXForwardedPath:{},xForwardedPath:{}",originXForwardedPath, xForwardedPath);
            request = request.mutate().path(xForwardedPath).build();
        }

        ServerHttpRequest newRequest = new ServerHttpRequestDecorator(request) {
            /*@Override
            public Flux<DataBuffer> getBody() {
                Flux<DataBuffer> body = this.getDelegate().getBody();
                return body.map(dataBuffer -> {
                    log.info("request:{}", dataBuffer.toString(StandardCharsets.UTF_8));
                    return dataBuffer;
                });
            }*/
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.set(HEADER_X_FORWARDED_FOR, customerIp);
                httpHeaders.set(Log4Constans.IP, customerIp);
                httpHeaders.set(Log4Constans.TRACE_ID, traceId);
                return httpHeaders;
            }
        };
        return newRequest;
    }
    /**
     * 获取真实客户端IP
     * @param serverHttpRequest
     * @return
     */
    public static String getRealIpAddress(ServerHttpRequest serverHttpRequest) {
        String ipAddress;
        try {
            // 1.根据常见的代理服务器转发的请求ip存放协议，从请求头获取原始请求ip。
            ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_X_FORWARDED_FOR);
            if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_PROXY_CLIENT_IP);
            }
            if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_WL_PROXY_CLIENT_IP);
            }

            // 2.如果没有转发的ip，则取当前通信的请求端的ip
            if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                InetSocketAddress inetSocketAddress = serverHttpRequest.getRemoteAddress();
                if(inetSocketAddress != null) {
                    ipAddress = inetSocketAddress.getAddress().getHostAddress();
                }
            }

            // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
            // "***.***.***.***"
            if (ipAddress != null) {
                ipAddress = ipAddress.split(SEPARATOR)[0].trim();
            }
        } catch (Exception e) {
            log.error("解析请求IP失败", e);
            ipAddress = "解析IP失败";
        }
        return ipAddress == null ? "解析IP失败" : ipAddress;
    }


}
