package org.xyzh.common.auth.filter;

import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.xyzh.common.auth.config.AuthProperties;
import org.xyzh.common.auth.contants.AuthContants;
import org.xyzh.common.auth.token.TokenParser;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.redis.service.RedisService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * @description    JWT认证过滤器，用于检测用户请求是否登录，支持白名单配置
 * @filename    JwtAuthenticationFilter.java
 * @author    yslg
 * @copyright yslg
 * @since 2025-11-09
 */
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
    
    private final TokenParser tokenParser;
    private final AuthProperties authProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private final RedisService redisService;
    private static final String REDIS_LOGIN_PREFIX = "login:token:";

    public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties) {
        this.tokenParser = tokenParser;
        this.authProperties = authProperties;
        this.redisService = null; // 占位，使用另一个构造函数注入
    }

    public JwtAuthenticationFilter(TokenParser tokenParser, AuthProperties authProperties, RedisService redisService) {
        this.tokenParser = tokenParser;
        this.authProperties = authProperties;
        this.redisService = redisService;
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) 
            throws ServletException, IOException {
        
        // 如果认证过滤器未启用，直接放行
        if (!authProperties.isEnabled()) {
            filterChain.doFilter(request, response);
            return;
        }
        
        String requestPath = request.getRequestURI();
        if (requestPath == null) {
            requestPath = "";
        }

        // 去掉 context-path，仅保留业务路径用于白名单匹配
        String contextPath = request.getContextPath();
        if (contextPath != null && !contextPath.isEmpty() && requestPath.startsWith(contextPath)) {
            requestPath = requestPath.substring(contextPath.length());
        }
        
        // 检查是否在白名单中
        if (isWhitelisted(requestPath)) {
            log.debug("请求路径在白名单中，跳过认证: {}", requestPath);
            filterChain.doFilter(request, response);
            return;
        }
        
        // 从请求头获取Token
        String token = extractToken(request);
        
        if (!StringUtils.hasText(token)) {
            log.warn("请求缺少Token: {}", requestPath);
            handleUnauthorized(response, "未提供认证令牌，请先登录");
            return;
        }
        
        try {
            // 验证Token
            if (tokenParser.isTokenExpired(token)) {
                log.warn("Token已过期: {}", requestPath);
                handleUnauthorized(response, "认证令牌已过期，请重新登录");
                return;
            }
            
            // 获取用户ID
            String userId = tokenParser.getUserIdFromToken(token);
            if (!StringUtils.hasText(userId)) {
                log.warn("Token中未找到用户ID: {}", requestPath);
                handleUnauthorized(response, "认证令牌无效");
                return;
            }
            
            // 验证Token有效性
            if (!tokenParser.validateToken(token, userId)) {
                log.warn("Token验证失败: userId={}, path={}", userId, requestPath);
                handleUnauthorized(response, "认证令牌验证失败");
                return;
            }
            
            // 将用户信息放入请求属性中，供后续使用
            Claims claims = tokenParser.getAllClaimsFromToken(token);
            request.setAttribute(AuthContants.USER_ID_ATTRIBUTE, userId);
            request.setAttribute(AuthContants.USERNAME_ATTRIBUTE, claims.get(AuthContants.CLAIMS_USERNAME_KEY, String.class));
            request.setAttribute(AuthContants.TOKEN_ATTRIBUTE, token);
            
            // 从Redis加载 LoginDomain，并将权限装配到 Spring Security 上下文
            if (redisService != null) {
                Object obj = redisService.get(REDIS_LOGIN_PREFIX + token);
                if (obj instanceof LoginDomain loginDomain) {
                    // 组装权限码 authorities（已存在）
                    List<SimpleGrantedAuthority> permAuthorities = null;
                    if (loginDomain.getUserPermissions() != null) {
                        permAuthorities = loginDomain.getUserPermissions().stream()
                            .filter(Objects::nonNull)
                            .map(TbSysPermissionDTO::getCode)
                            .filter(StringUtils::hasText)
                            .map(SimpleGrantedAuthority::new)
                            .toList();
                    }

                    // 组装角色 authorities（关键：ROLE_ 前缀）
                    List<SimpleGrantedAuthority> roleAuthorities = null;
                    if (loginDomain.getUserRoles() != null) {
                        roleAuthorities = loginDomain.getUserRoles().stream()
                            .filter(Objects::nonNull)
                            .map(r -> r.getRoleId())              // 若有角色code/名称，可替换为对应字段
                            .filter(StringUtils::hasText)
                            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                            .toList();
                    }

                    // 合并权限与角色
                    List<SimpleGrantedAuthority> authorities = Stream
                        .concat(
                            permAuthorities != null ? permAuthorities.stream() : Stream.empty(),
                            roleAuthorities != null ? roleAuthorities.stream() : Stream.empty()
                        )
                        .toList();
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(loginDomain, null, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            
            log.debug("Token验证成功: userId={}, path={}", userId, requestPath);
            
            // 继续执行过滤器链
            filterChain.doFilter(request, response);
            
        } catch (Exception e) {
            log.error("Token解析或验证异常: path={}, error={}", requestPath, e.getMessage(), e);
            handleUnauthorized(response, "认证令牌解析失败: " + e.getMessage());
        }
    }
    
    /**
     * 检查路径是否在白名单中
     */
    private Boolean isWhitelisted(@NonNull String path) {
        // 1. 先检查认证相关接口（login / logout / captcha / refresh）
        if (authProperties.getAuthPaths() != null) {
            for (String pattern : authProperties.getAuthPaths()) {
                if (pattern != null && pathMatcher.match(pattern, path)) {
                    return true;
                }
            }
        }

        // 2. 再检查通用白名单
        if (authProperties.getWhitelist() != null) {
            for (String pattern : authProperties.getWhitelist()) {
                if (pattern != null && pathMatcher.match(pattern, path)) {
                    return true;
                }
            }
        }

        return false;
    }
    
    /**
     * 从请求头中提取Token
     */
    private String extractToken(HttpServletRequest request) {
        String header = request.getHeader(authProperties.getTokenHeader());
        
        if (!StringUtils.hasText(header)) {
            return null;
        }
        
        // 支持 Bearer 前缀
        String prefix = authProperties.getTokenPrefix();
        if (StringUtils.hasText(prefix) && header.startsWith(prefix)) {
            return header.substring(prefix.length()).trim();
        }
        
        // 也支持直接传递Token（不带前缀）
        return header.trim();
    }
    
    /**
     * 处理未授权响应
     */
    private void handleUnauthorized(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        
        ResultDomain<Object> result = ResultDomain.failure(HttpStatus.UNAUTHORIZED.value(), message);
        String json = JSON.toJSONString(result);
        
        response.getWriter().write(json);
        response.getWriter().flush();
    }
}
