package org.xyzh.auth.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.xyzh.auth.util.JwtTokenUtil;
import org.xyzh.auth.config.AuthProperties;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.redis.service.RedisService;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @description    JwtAuthenticationFilter.java文件描述 JWT认证过滤器
 * @filename    JwtAuthenticationFilter.java
 * @author    yslg
 * @copyright xyzh
 * @since 2025-09-28
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private static final String REDIS_LOGIN_PREFIX = "login:token:";

    /**
     * @description    UV统计key前缀，例如 stat:uv:20251114
     */
    private static final String REDIS_UV_PREFIX = "stat:uv:";

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private AuthProperties authProperties;

    @Autowired
    private RedisService redisService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        
        String requestURI = request.getRequestURI();
        
        // 检查是否在白名单中
        if (isWhitelisted(requestURI)) {
            filterChain.doFilter(request, response);
            return;
        }

        String token = getTokenFromRequest(request);
        
        if (StringUtils.hasText(token)) {
            try {
                String userId = jwtTokenUtil.getUserIdFromToken(token);
                
                if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    // 验证token有效性
                    if (!jwtTokenUtil.validateToken(token, userId)) {
                        logger.warn("Token验证失败，userId: {}", userId);
                        filterChain.doFilter(request, response);
                        return;
                    }
                    
                    // 【优化】从Redis缓存中获取LoginDomain，避免每次都查数据库
                    // 多设备登录场景下，以token为维度存储和获取会话信息
                    String redisKey = REDIS_LOGIN_PREFIX + token;
                    LoginDomain loginDomain = (LoginDomain) redisService.get(redisKey);
                    
                    if (loginDomain != null && loginDomain.getUser() != null) {
                        // 【优化】直接使用缓存中的用户信息构建UserDetails，不查数据库
                        // 使用UserPrincipal从LoginDomain创建UserDetails
                        UserDetails userDetails = org.xyzh.auth.domain.UserPrincipal.createFromLoginDomain(
                            loginDomain.getUser(), 
                            loginDomain.getRoles(), 
                            loginDomain.getPermissions()
                        );
                        
                        UsernamePasswordAuthenticationToken authentication = 
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);

                        // 记录UV：以用户ID为维度，按天去重
                        try {
                            String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
                            String uvKey = REDIS_UV_PREFIX + today;
                            redisService.sAdd(uvKey, userId);
                        } catch (Exception e) {
                            logger.warn("记录UV失败, userId={}: {}", userId, e.getMessage());
                        }
                    } else {
                        logger.warn("Redis缓存中未找到用户登录信息，userId: {}, 可能已过期或未登录", userId);
                    }
                }
            } catch (Exception e) {
                logger.error("JWT token validation failed: " + e.getMessage(), e);
            }
        }

        filterChain.doFilter(request, response);
    }

    /**
     * @description    从请求中获取Token
     * 支持以下方式获取Token：
     * 1. Header中的Authorization: Bearer {token}
     * 2. URL参数中的token: ?token={token}
     * 3. URL参数中的access_token: ?access_token={token}
     * @param    request HTTP请求
     * @return    String Token
     * @author    yslg
     * @since 2025-09-28
     */
    private String getTokenFromRequest(HttpServletRequest request) {
        // 1. 优先从Header中获取Token
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        
        // 2. 从URL参数token中获取
        String token = request.getParameter("token");
        if (StringUtils.hasText(token)) {
            return token;
        }
        
        // 3. 从URL参数access_token中获取
        String accessToken = request.getParameter("access_token");
        if (StringUtils.hasText(accessToken)) {
            return accessToken;
        }
        
        return null;
    }

    /**
     * @description    检查请求路径是否在白名单中
     * @param    requestURI 请求URI
     * @return    boolean 是否在白名单中
     * @author    yslg
     * @since 2025-09-28
     */
    private boolean isWhitelisted(String requestURI) {
        return authProperties.getWhiteList().stream()
                .anyMatch(whitePath -> requestURI.matches(whitePath.replace("*", ".*")));
    }
}
