package com.backendsys.modules.common.config.security.filter;

import cn.hutool.core.util.ArrayUtil;
import com.backendsys.modules.common.config.security.annotations.AnonymousProperties;
import com.backendsys.modules.common.utils.CookieUtil;
import com.backendsys.utils.response.ResultEnumService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.backendsys.utils.response.Result;
import com.backendsys.utils.response.ResultEnum;
import com.backendsys.modules.common.config.security.utils.JwtUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;

/**
 * TODO 一、JWT身份验证过滤器
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/3 10:56
 */
@Component
// 使用final，将服务注入class
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 需要每次收到请求的时候，过滤器都处于活动状态
     * 因此每次用户发送请求时希望过滤器被触发并完成要做的所有工作
     */
    private final JwtUtil jwtUtil;
    /**
     * 加载用户特定数据的核心接口。
     * 它作为用户DAO在整个框架中使用，并且是DaoAuthenticationProvider使用的策略
     */
    private final UserDetailsService userDetailsService; // 从ApplicationConfig中创建的Bean对象获取

    @Autowired
    private ResultEnumService resultEnumService;

    @Value("#{'${whitelist.static}'.split(',')}")
    private final String[] STATIC_WHITELIST;
    @Value("#{'${whitelist.jwt}'.split(',')}")
    private final String[] JWT_WHITELIST;

    @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
    private String REDIS_LOGIN_TOKEN_PREFIX;

    @Value("${DEFAULT_LANGUAGE}")
    private String DEFAULT_LANGUAGE;

    @Autowired
    private AnonymousProperties anonymousProperties;

    /**
     * 校验路径是否是要忽略鉴权路径
     * @param url
     */
    private boolean isPassWithRule(String url){

        // 使用了 @Anonymous 注解的地址不需要鉴权
        String[] anonymousUrls = anonymousProperties.getUrls();

        // 在配置文件中的 白名单地址
        String[] whiteUrls = ArrayUtil.addAll(JWT_WHITELIST, STATIC_WHITELIST, anonymousUrls);

        for (String item : whiteUrls) {
            String method = item.toString().trim();
            if (Arrays.asList("/**", "**").contains(method)) return false;
            if (method.endsWith("/**")) {
                String prefix = method.substring(0, method.length() - 3);
                if (url.startsWith(prefix + "/")) {
                    return true;
                }
            } else if (url.equals(method.toString().trim())) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, MalformedJwtException {

        // -- 放行 ------------------------------------------------------------------------------
        // - 获取当前请求的URI，检查是否放行 (白名单)
        // - 如果不是 /api 路由，则直接放行
        Boolean isNotAPIUrI = !request.getRequestURI().startsWith("/api");
        if (isPassWithRule(request.getRequestURI()) || isNotAPIUrI) {
            filterChain.doFilter(request, response);
            return;
        }

        // -- 鉴权 ------------------------------------------------------------------------------

        // [Header][拦截] 从请求头中获取认证信息
        final String authHeader = request.getHeader("Authorization");
        if(authHeader == null || !authHeader.startsWith("Bearer ")) {
            // [TOKEN_EMPTY_ERROR: 请先登录获取有效的Token]
            handleError(request, response, ResultEnum.TOKEN_EMPTY_ERROR);
            return;
        }

        // 从 token 中解析出 username (用户ID)
        try {
            final String jwt = authHeader.substring(7);
            final String username = jwtUtil.extractUsername(jwt);

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
                // 根据jwt解析出来的 username (id)，获取数据库中的用户信息，封装UserDetails对象
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                // System.out.println(userDetails);
                // Username=14, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin, normal]

                // // 区分 [系统用户] 和 [会员用户] 不同的 Token Redis Key 命名
                // Claims claims = jwtUtil.extractAllClaims(jwt);
                // System.out.println(claims);
                // String target = (String) claims.get("target");
                // String redisTag = "";
                // if ("System".equals(target)) redisTag = "token:suid:";
                // if ("Member".equals(target)) redisTag = "token:muid:";
                // String tokenRedisKey = redisTag + userId;

                // 判断 Redis Token 是否存在
                String redisKey = REDIS_LOGIN_TOKEN_PREFIX + userDetails.getUsername();
                Boolean isTokenValid = (stringRedisTemplate.opsForValue().get(redisKey) != null);
                if (!isTokenValid) {
                    // [TOKEN_INVALID: Token已失效，请重新登录]
                    handleError(request, response, ResultEnum.TOKEN_INVALID);
                    return ;
                }

                if (jwtUtil.isTokenValid(jwt, userDetails) && isTokenValid) {

                    // 如果令牌有效，封装一个 UsernamePasswordAuthenticationToken 对象
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                    // WebAuthenticationDetailsSource: { RemoteIpAddress, SessionId }
                    // authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    // 将 Token 存入 SecurityContextHolder.getContext().getAuthentication() 的 getDetail 中
                    String token = authHeader.replace("Bearer ", "");
                    authentication.setDetails(token);

                    // 更新安全上下文的持有用户
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
                filterChain.doFilter(request, response);
            }

        } catch (MalformedJwtException e) {
            // [TOKEN_FORMAT_EXCEPTION: Token格式异常]
            handleError(request, response, ResultEnum.TOKEN_FORMAT_EXCEPTION);
        } catch (ExpiredJwtException e) {
            // [TOKEN_EXPIRED: Token过期]
            handleError(request, response, ResultEnum.TOKEN_EXPIRED);
        } catch (SignatureException e) {
            // [TOKEN_FORMAT_INCORRECT: Token格式不正确]
            handleError(request, response, ResultEnum.TOKEN_FORMAT_INCORRECT);
        }

    }

    private void handleError(HttpServletRequest request, HttpServletResponse response, ResultEnum resultEnumKey) throws IOException {

        Result result = Result.error(resultEnumKey.getCode(), resultEnumService.getMessage(resultEnumKey));
//        Result result = Result.error(resultEnum.getCode(), resultEnum.getMessage());

        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }

}
