package com.github.wxiaoqi.security.gate.filter;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultClientResponse;
import org.springframework.core.Ordered;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.function.Consumer;
import java.util.function.Function;

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

Class inClass = String.class;

Class outClass = String.class;

private static Decoder decoder = new Jackson2JsonDecoder();

private static Encoder encoder = new Jackson2JsonEncoder();

private static final ExchangeStrategies STRATEGIES;


    static {

                STRATEGIES = ExchangeStrategies.builder().codecs(new Consumer<ClientCodecConfigurer>() {

                @Override
                public void accept(ClientCodecConfigurer clientCodecConfigurer) {
                    ClientCodecConfigurer.ClientDefaultCodecs clientDefaultCodecs = clientCodecConfigurer.defaultCodecs();
                    clientDefaultCodecs.jackson2JsonDecoder(decoder);
                    clientDefaultCodecs.jackson2JsonEncoder(encoder);
                }
        }).build();
    }

    @Override

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

    // 响应报文打印

    Function responseRead = body -> {
        log.info("===============响应体：body = {}", body);
        return Mono.just(body);
    };

    ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {

    @Override

    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json");
        ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
        DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, STRATEGIES);
        Mono modifiedBody = clientResponse.bodyToMono(inClass).flatMap(responseRead);
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                Flux<DataBuffer> messageBody = outputMessage.getBody();
                HttpHeaders headers = this.getDelegate().getHeaders();
                if (!headers.containsKey("Transfer-Encoding")) {
                    messageBody = messageBody.doOnNext((data) -> {
                        headers.setContentLength((long) data.readableByteCount());
                 });
             }
        return this.getDelegate().writeWith(messageBody);
    }));
    }

        @Override
        public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return this.writeWith(Flux.from(body).flatMapSequential((p) -> {
                        return p;
                    }));
                }
            };
            return chain.filter(exchange.mutate().response(responseDecorator).build());
        }
        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE;

        }

    public static class ResponseAdapter implements ClientHttpResponse {
            private final Flux<DataBuffer> flux;
            private final HttpHeaders headers;

            public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {

                this.headers = headers;
                if (body instanceof Flux) {
                    this.flux = (Flux) body;
                } else {
                    this.flux = ((Mono) body).flux();
                }
        }
        @Override
        public Flux<DataBuffer> getBody() {
            return this.flux;
        }
        @Override
        public HttpHeaders getHeaders() {
             return this.headers;
        }

        @Override
        public HttpStatus getStatusCode() {
            return null;
        }
        @Override
        public int getRawStatusCode() {
            return 0;
        }

        @Override
        public MultiValueMap<String, ResponseCookie> getCookies() {
             return null;
        }
    }
}
