package com.inspect.gateway.filter;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class RequestParamGlobalFilter implements GlobalFilter, Ordered {
    private Logger logger = LoggerFactory.getLogger(RequestParamGlobalFilter.class);
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    private static final List<String> PATH_WHITE_LIST = Arrays.asList(
            "/patrol/task/add",
            "/patrol/task/save",
            "/patrol/resultmain/reportExport",
            "/patrol/task/taskInfoExport",
            "/basedata/prodevmnt/device/treeProMainDevicePatrolEquSelect",
            "/example/path2"
    );

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        MediaType contentType = headers.getContentType();
        long contentLength = headers.getContentLength();
        final String path = request.getURI().getPath();
        boolean bWhite = isWhiteListed(path);
        if (contentLength <= 0L
                || !MediaType.APPLICATION_JSON.equals(contentType) && !MediaType.APPLICATION_JSON_UTF8.equals(contentType)
                || bWhite) {
            logger.info("RequestParamGlobalFilter body transfer omitting, path: {}, bWhite: {}", path, bWhite);
            return chain.filter(exchange);
        }

        return readBody(exchange, chain);
    }

    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap((dataBuffer) -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            final Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class).doOnNext((objectValue) -> {
            }).then(chain.filter(mutatedExchange));
        });
    }

    public int getOrder() {
        return Integer.MIN_VALUE;
    }

    private boolean isWhiteListed(String path) {
        return PATH_WHITE_LIST.stream().anyMatch(path::startsWith);
    }
}
