package com.seer.teach.filter.auth;

import cn.hutool.json.JSONUtil;
import com.seer.teach.common.ResultBean;
import com.seer.teach.config.AuthProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
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.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.nio.charset.StandardCharsets;

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

    private AuthProperties authProperties;
    private WebClient webClient;

    public OpenApiAuthFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction, AuthProperties authProperties) {
        this.webClient = WebClient.builder().filter(lbFunction).build();
        this.authProperties = authProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();
        HttpMethod method = request.getMethod();

        if (!path.contains("/seer/open/rest/oauth/token")) {
            return chain.filter(exchange);
        }

        // 读取请求头
        String appId = request.getHeaders().getFirst("appId");
        String sign = request.getHeaders().getFirst("sign");
        String timestamp = request.getHeaders().getFirst("timestamp");

        if (!StringUtils.hasText(appId) || !StringUtils.hasText(sign) || !StringUtils.hasText(timestamp)) {
            log.info("OpenApiAuthFilter2 缺少签名参数");
            return unauthorized(exchange, "缺少必要的签名参数");
        }

        long reqTime;
        try {
            reqTime = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            log.info("OpenApiAuthFilter2 时间戳格式错误");
            return unauthorized(exchange, "时间戳格式错误");
        }

        if (System.currentTimeMillis() - reqTime > 5 * 60 * 1000) {
            log.info("OpenApiAuthFilter2 请求已过期");
            return unauthorized(exchange, "请求已过期");
        }

        // GET 请求通常没有 body
        if (method == HttpMethod.GET) {
            return verifyAndForward(exchange, chain, "", appId, sign, timestamp);
        }

        // POST/PUT 请求读取 body
        return DataBufferUtils.join(request.getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);

                    String requestBody = new String(bytes, StandardCharsets.UTF_8);
                    log.info("OpenApiAuthFilter2 请求体: {}", requestBody);

                    return verifyAndForward(exchange, chain, requestBody, appId, sign, timestamp);
                });
    }

    private Mono<Void> verifyAndForward(ServerWebExchange exchange, GatewayFilterChain chain,
                                        String requestBody, String appId, String sign, String timestamp) {
        String url = UriComponentsBuilder.fromHttpUrl(authProperties.getSignApi())
                .queryParam("appId", appId)
                .queryParam("sign", sign)
                .queryParam("timestamp", timestamp)
                .queryParam("body", requestBody)
                .build(true)
                .toUriString();

        return Mono.fromCallable(() -> url)
                .subscribeOn(Schedulers.boundedElastic())
                .flatMap(uri -> webClient.get()
                        .uri(uri)
                        .retrieve()
                        .bodyToMono(ResultBean.class))
                .flatMap(result -> {
                    if (!result.isSuccess()) {
                        log.info("OpenApiAuthFilter2 验签失败: {}", result.getMsg());
                        return unauthorized(exchange, result.getMsg());
                    }

                    String decryptedBody = (String) result.getData();
                    log.info("OpenApiAuthFilter2 解密后请求体: {}", decryptedBody);
                    byte[] newBodyBytes = decryptedBody.getBytes(StandardCharsets.UTF_8);

                    ServerHttpRequest newRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(newBodyBytes);
                            return Flux.just(buffer);
                        }

                        @Override
                        public HttpHeaders getHeaders() {
                            HttpHeaders headers = new HttpHeaders();
                            headers.putAll(super.getHeaders());
                            headers.setContentLength(newBodyBytes.length);
                            headers.setContentType(MediaType.APPLICATION_JSON);
                            return headers;
                        }
                    };

                    ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
                    return chain.filter(newExchange);
                });
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String body = JSONUtil.toJsonStr(new ResultBean<>(401, "Unauthorized", message));
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}
