package study.gateway;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@SpringBootConfiguration
@EnableAutoConfiguration
@org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients(defaultConfiguration=LabLoadBalancer.class)
public class GatewayApplication {
    @Bean TraceGlobal firstGlobal() { return new TraceGlobal(0,"G0"); }
    @Bean TraceGlobal secondGlobal() { return new TraceGlobal(20,"G20"); }
    @Bean JwtMaterial jwtMaterial() throws Exception { return new JwtMaterial(); }
    @Bean LabDirectory directory() { return new LabDirectory(); }
    @Bean(destroyMethod="close") Upstreams upstreams(JwtMaterial keys) throws Exception { return new Upstreams(keys); }
    @Bean(destroyMethod="close") RateLimitResources rates(org.springframework.cloud.gateway.support.ConfigurationService configuration,
            @org.springframework.beans.factory.annotation.Qualifier("redisRequestRateLimiterScript")
            org.springframework.data.redis.core.script.RedisScript<java.util.List<Long>> script,
            org.springframework.data.redis.core.ReactiveStringRedisTemplate template) throws Exception {
        return new RateLimitResources(configuration,script,template);
    }
    @Bean RouteLocator routes(RouteLocatorBuilder builder, Upstreams peers, RateLimitResources rates) {
        var routes=builder.routes()
                .route(rates.routeId, r -> r.path("/limited/**").filters(f -> f.stripPrefix(1)
                        .requestRateLimiter(c -> c.setRateLimiter(rates.limiter))).uri(peers.uri()))
                .route(rates.emptyRouteId, r -> r.path("/empty-key").filters(f -> f
                        .requestRateLimiter(c -> c.setRateLimiter(rates.limiter))).uri(peers.uri()))
                .route("first", r -> r.order(-10).path("/api/**")
                        .filters(f -> f.stripPrefix(1).addRequestHeader("X-Lab-Label", "first")
                                .filter(TraceGlobal.route("R10"),10).filter(TraceGlobal.route("R30"),30))
                        .uri(peers.uri()))
                .route("later", r -> r.order(10).path("/api/**")
                        .filters(f -> f.addRequestHeader("X-Lab-Label", "later"))
                        .uri(peers.uri()))
                .route("slow-head", r -> r.path("/timeout/head").filters(f -> f.setPath("/slow-head"))
                        .metadata("response-timeout",100).uri(peers.uri()))
                .route("slow-body", r -> r.path("/timeout/body").filters(f -> f.setPath("/slow-body"))
                        .metadata("response-timeout",100).uri(peers.uri()))
                .route("retry", r -> r.path("/retry/**").filters(f -> f.stripPrefix(1).prefixPath("/retry-fail").retry(3))
                        .uri(peers.uri()))
                .route("no-retry", r -> r.path("/no-retry").filters(f -> f.setPath("/retry-fail/without"))
                        .uri(peers.uri()))
                .route("secure", r -> r.path("/secure/**").filters(f -> f.stripPrefix(1)
                        .removeRequestHeader("X-User").removeRequestHeader("X-Verified-User")
                        .filter((exchange,chain) -> exchange.getPrincipal().flatMap(principal ->
                                chain.filter(exchange.mutate().request(request ->
                                        request.header("X-Verified-User",principal.getName())).build()))))
                        .uri(peers.uri()))
                ;
        for (String service : java.util.List.of("round","direct","cached","health","zone","hint","sticky","weighted","subset")) {
            routes.route("lb-"+service,r -> r.path("/lb/"+service+"/**").filters(f -> f.stripPrefix(2)).uri("lb://"+service));
        }
        return routes.build();
    }
    @Bean org.springframework.security.oauth2.jwt.ReactiveJwtDecoder decoder(JwtMaterial keys) throws Exception {
        var decoder=org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withPublicKey(keys.publicKey()).build();
        org.springframework.security.oauth2.core.OAuth2TokenValidator<org.springframework.security.oauth2.jwt.Jwt> audience =
                jwt -> jwt.getAudience().contains("u32-api")
                        ? org.springframework.security.oauth2.core.OAuth2TokenValidatorResult.success()
                        : org.springframework.security.oauth2.core.OAuth2TokenValidatorResult.failure(
                                new org.springframework.security.oauth2.core.OAuth2Error("invalid_token"));
        decoder.setJwtValidator(new org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator<>(
                new org.springframework.security.oauth2.jwt.JwtTimestampValidator(java.time.Duration.ZERO),
                new org.springframework.security.oauth2.jwt.JwtIssuerValidator("https://issuer.example.test"),audience));
        return decoder;
    }
    @Bean SecurityWebFilterChain access(ServerHttpSecurity http) {
        // Bearer-only API lab: there is no browser session or cookie authentication,
        // so CSRF is not part of this threat model. Keeping it on would turn every
        // POST into 403 before the route could observe the retry method set.
        return http.csrf(org.springframework.security.config.web.server.ServerHttpSecurity.CsrfSpec::disable)
                .authorizeExchange(a -> a.pathMatchers("/actuator/**").hasAuthority("SCOPE_ops")
                        .pathMatchers("/secure/**","/limited/**").hasAuthority("SCOPE_read")
                        .anyExchange().permitAll())
                .oauth2ResourceServer(o -> o.jwt(org.springframework.security.config.Customizer.withDefaults())).build();
    }
}
