package com.red.circle.gateway.filter.factory;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.Setter;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * Description:
 * @author 金泽强
 * @date 2024/10/28 上午12:55
 */
public class ForwardRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<ForwardRouteGatewayFilterFactory.Config> {

    private ApplicationContext applicationContext;

    public static final String FORWARD_GATEWAY_MATCH = "forwardGatewayMatch";

    @Resource
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public ForwardRouteGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {

            ServerHttpRequest request = exchange.getRequest();
            URI uri = request.getURI();
            String requestPath = uri.getRawPath();
            AES aes = new AES(Mode.ECB, Padding.ZeroPadding, config.aesKey.getBytes(StandardCharsets.UTF_8));
            final String forwardPath = "/" + aes.decryptStr(requestPath.substring(1));

            RouteLocator routeLocator = applicationContext.getBean(RouteLocator.class);

            ServerHttpRequest newRequest = request.mutate().path(forwardPath).build();
            AtomicReference<ServerWebExchange> newExchange = new AtomicReference<>(exchange.mutate().request(newRequest).build());


            PathContainer pathContainer = PathContainer.parsePath(forwardPath);
            newExchange.get().getAttributes().put(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, pathContainer);

            Flux<Route> routes = routeLocator.getRoutes();
            routes.subscribe(route -> {
                if ("service-gateway".equals(route.getId())) {
                    return;
                }
                AsyncPredicate<ServerWebExchange> predicate = route.getPredicate();
                Publisher<Boolean> apply = predicate.apply(newExchange.get());
                Mono.from(apply)
                        .subscribe(result -> {
                            if (result) {
                                if (!newExchange.get().getAttributeOrDefault(FORWARD_GATEWAY_MATCH, false)) {
                                    newExchange.get().getAttributes().put(FORWARD_GATEWAY_MATCH, true);
                                    newExchange.get().getAttributes().put(GATEWAY_ROUTE_ATTR, route);

                                    List<String> stripPrefixList = Arrays.asList("/auth/", "/external/", "/order/", "/wallet/", "/live/");
                                    Optional<String> first = stripPrefixList.stream().filter(stripPrefix -> forwardPath.startsWith(stripPrefix)).findFirst();
                                    ServerHttpRequest newRequest1 = newRequest;
                                    if (first.isPresent()) {
                                        newRequest1 =  request.mutate().path("/" + forwardPath.replace(first.get(), "")).build();
                                    }
                                    newExchange.get().getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest1.getURI());
                                    newExchange.set(newExchange.get().mutate().request(newRequest1).build());
                                }
                            }
                        });
            });
            return chain.filter(newExchange.get());
        };
    }

    @Setter
    @Getter
    public static class Config {
        private String aesKey;

    }
}
