package com.xindazhou.gateway.filter;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.xindazhou.framework.common.util.json.JsonUtils;
import com.xindazhou.gateway.config.GatewayPublicEndpointsProperties;
import com.xindazhou.gateway.core.LoginUser;
import com.xindazhou.gateway.core.jwt.GatewayJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Gateway 认证过滤器
 * 根据路径判断是否为公开接口，公开接口跳过认证，私有接口需要验证 Token
 *
 * @author xindazhou
 */
@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    @Autowired
    @Qualifier("gatewayPublicEndpointsProperties")
    private GatewayPublicEndpointsProperties gatewayProperties;

    private final Set<String> publicEndpoints = new HashSet<>();
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * Token 请求头名称
     */
    private static final String TOKEN_HEADER = "Authorization";
    private static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 初始化公开接口列表
     * 使用 @PostConstruct 确保在 Bean 创建后立即初始化
     */
    @PostConstruct
    public void initPublicEndpoints() {
        log.info("开始初始化 Gateway 公开接口列表...");
        
        List<String> configList = gatewayProperties.getPublicEndpoints();
        log.debug("配置值: {}", configList);
        
        if (configList != null && !configList.isEmpty()) {
            for (String endpoint : configList) {
                String trimmed = endpoint.trim();
                if (StringUtils.hasText(trimmed)) {
                    publicEndpoints.add(trimmed);
                }
            }
        } else {
            log.warn("Gateway 公开接口配置为空，所有接口都需要 Token 认证");
        }
        
        log.info("Gateway 公开接口列表初始化完成，共 {} 个接口", publicEndpoints.size());
        if (log.isDebugEnabled()) {
            publicEndpoints.forEach(endpoint -> log.debug("公开接口: {}", endpoint));
        } else {
            // 即使不是 debug 级别，也输出前几个接口作为确认
            publicEndpoints.stream().limit(5).forEach(endpoint -> log.info("  公开接口: {}", endpoint));
            if (publicEndpoints.size() > 5) {
                log.info("  ... 还有 {} 个接口", publicEndpoints.size() - 5);
            }
        }
    }

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

        // 检查是否为公开接口
        if (isPublicEndpoint(path)) {
            log.debug("公开接口，跳过认证: {}", path);
            return chain.filter(exchange);
        }

        // 私有接口，需要验证 Token
        String token = extractToken(request);
        if (!StringUtils.hasText(token)) {
            log.warn("私有接口缺少 Token: {}", path);
            return unauthorized(exchange, "缺少访问令牌");
        }

        // 验证 Token 有效性（JWT 本地验证）
        LoginUser loginUser = verifyTokenAndBuildLoginUser(token, path);
        if (loginUser == null) {
            log.warn("Token 验证失败: {}", path);
            return unauthorized(exchange, "访问令牌无效或已过期");
        }

        // 将用户信息通过 header 传递给下游服务
        // 同时保留 Token，以便降级方案使用
        ServerHttpRequest modifiedRequest = request.mutate()
                .header(TOKEN_HEADER, TOKEN_PREFIX + token)  // 保留 Token
                .header("login-user", encodeLoginUser(loginUser))  // 传递用户信息（与下游服务约定的 header 名称）
                .build();

        log.debug("Gateway Token 验证成功，已设置 login-user header: userId={}, path={}", loginUser.getId(), path);
        return chain.filter(exchange.mutate().request(modifiedRequest).build());
    }

    /**
     * 判断是否为公开接口
     */
    private boolean isPublicEndpoint(String path) {
        // 检查精确匹配
        if (publicEndpoints.contains(path)) {
            return true;
        }

        // 检查 Ant 路径模式匹配
        for (String pattern : publicEndpoints) {
            if (pathMatcher.match(pattern, path)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 从请求中提取 Token
     */
    private String extractToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst(TOKEN_HEADER);
        if (StringUtils.hasText(authHeader) && authHeader.startsWith(TOKEN_PREFIX)) {
            return authHeader.substring(TOKEN_PREFIX.length());
        }
        return null;
    }

    /**
     * 返回未授权响应
     */
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        
        String body = String.format("{\"code\":401,\"msg\":\"%s\",\"data\":null}", message);
        return response.writeWith(
                Mono.just(response.bufferFactory().wrap(body.getBytes()))
        );
    }

    /**
     * 验证 Token 并构建 LoginUser 对象
     *
     * @param token JWT Token
     * @param path  请求路径（用于日志）
     * @return LoginUser 对象，验证失败返回 null
     */
    private LoginUser verifyTokenAndBuildLoginUser(String token, String path) {
        try {
            // 1. 使用 JWT 本地验证 Token
            DecodedJWT jwt = GatewayJwtUtil.verifyToken(token);
            if (jwt == null) {
                log.warn("JWT Token 验证失败: path={}", path);
                return null;
            }

            // 2. 从 JWT 中提取用户信息
            Long userId = GatewayJwtUtil.getUserId(jwt);
            Integer userType = GatewayJwtUtil.getUserType(jwt);
            Long tenantId = GatewayJwtUtil.getTenantId(jwt);
            List<String> scopes = GatewayJwtUtil.getScopes(jwt);
            java.time.LocalDateTime expiresTime = GatewayJwtUtil.getExpiresTime(jwt);

            if (userId == null || userType == null) {
                log.warn("JWT Token 中缺少用户信息: userId={}, userType={}, path={}", userId, userType, path);
                return null;
            }

            // 3. 构建 LoginUser 对象
            LoginUser loginUser = new LoginUser();
            loginUser.setId(userId);
            loginUser.setUserType(userType);
            loginUser.setTenantId(tenantId);
            loginUser.setScopes(scopes);
            loginUser.setExpiresTime(expiresTime);

            log.debug("Gateway JWT Token 验证成功: userId={}, userType={}, path={}", userId, userType, path);
            return loginUser;

        } catch (Exception e) {
            log.error("Gateway Token 验证异常: path={}, error={}", path, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 将 LoginUser 对象编码为 header 值
     * 使用 URL 编码避免特殊字符问题
     *
     * @param loginUser LoginUser 对象
     * @return 编码后的字符串
     */
    private String encodeLoginUser(LoginUser loginUser) {
        try {
            String json = JsonUtils.toJsonString(loginUser);
            // URL 编码，解决中文和特殊字符问题
            return URLEncoder.encode(json, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("编码 LoginUser 失败: {}", e.getMessage(), e);
            return "";
        }
    }

    @Override
    public int getOrder() {
        // 设置过滤器执行顺序，数字越小越先执行
        // 认证过滤器应该在路由之前执行
        return -100;
    }
}

