package com.njcn.product.auth.filter;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.njcn.common.pojo.constant.SecurityConstants;
import com.njcn.common.pojo.dto.UserTokenInfo;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;

import com.njcn.common.utils.HttpResultUtil;
import com.njcn.redis.utils.RedisUtil;
import com.njcn.web.utils.IpUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.util.Strings;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 全局过滤器
 *
 * @author hongawen
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter extends OncePerRequestFilter {

    private final static List<String> REPALCEURL = Arrays.asList("/user-boot","/system-boot","/device-boot","/advance-boot");

    private final RedisUtil redisUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        ServerHttpRequest serverHttpRequest = new ServletServerHttpRequest(request);

        Map<String, String> customHeaders = new HashMap<>();
        String realIp = IpUtils.getRealIpAddress(serverHttpRequest);
        String token = request.getHeader(SecurityConstants.AUTHORIZATION_KEY);
        String path = request.getRequestURI();
        for (String s : REPALCEURL) {
            if(path.contains(s)){
                path =   path.replace(s,"");
            }
        }
        if (StrUtil.isBlank(token) || !token.startsWith(SecurityConstants.AUTHORIZATION_PREFIX)) {
            customHeaders.put(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp);
            chain.doFilter(new ModifiedHttpServletRequest(request, path, customHeaders), response);
            return;

        }
        token = token.replace(SecurityConstants.AUTHORIZATION_PREFIX, Strings.EMPTY);
        JWSObject jwsObject = null;
        try {
            jwsObject = JWSObject.parse(token);
        } catch (ParseException e) {
            sendErrorResponse(response,CommonResponseEnum.PARSE_TOKEN_ERROR);
            return;
        }
        String payload = jwsObject.getPayload().toString();
        JSONObject jsonObject = JSONUtil.parseObj(payload);
        String userIndex = jsonObject.getStr(SecurityConstants.USER_INDEX_KEY);
        String jti = jsonObject.getStr(SecurityConstants.JWT_JTI);
        String exp = jsonObject.getStr(SecurityConstants.JWT_EXP);
        LocalDateTime expTime = LocalDateTimeUtil.of(Long.parseLong(exp + "000"));
        if(expTime.isBefore(LocalDateTimeUtil.now())){
            sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT);
            return;
        }
        String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
        List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
        if (CollectionUtils.isNotEmpty(blackUsers)) {
            for (UserTokenInfo blackUser : blackUsers) {
                //存在当前的刷新token，则抛出业务异常
                if(blackUser.getAccessTokenJti().equalsIgnoreCase(jti)){
                    sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT);
                    return;
                }
            }
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(request.getUserPrincipal(),null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        customHeaders.put(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, StandardCharsets.UTF_8.toString()));
        customHeaders.put(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp);
        chain.doFilter(new ModifiedHttpServletRequest(request, path, customHeaders), response);

    }

    private void sendErrorResponse(HttpServletResponse response, CommonResponseEnum error) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/json;charset=UTF-8");
        response.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Cache-Control", "no-cache");
        HttpResult<String> httpResult = new HttpResult<>();
        httpResult.setCode(error.getCode());
        httpResult.setMessage(error.getMessage());

        response.getWriter().write(new JSONObject(httpResult, false).toString());
    }

    // 自定义HttpServletRequest包装器
    private static class ModifiedHttpServletRequest extends HttpServletRequestWrapper {
        private final String modifiedUri;

        public ModifiedHttpServletRequest(HttpServletRequest request, String modifiedUri, Map<String, String> customHeaders) {
            super(request);
            this.modifiedUri = modifiedUri;
            this.customHeaders = customHeaders;
        }

        @Override
        public String getRequestURI() {
            return modifiedUri;
        }

        @Override
        public String getServletPath() {
            return modifiedUri;
        }

        private final Map<String, String> customHeaders;


        // 添加/修改Header
        public void putHeader(String name, String value) {
            this.customHeaders.put(name, value);
        }

        @Override
        public String getHeader(String name) {
            String customValue = customHeaders.get(name);
            return (customValue != null) ? customValue : super.getHeader(name);
        }

        @Override
        public Enumeration<String> getHeaders(String name) {
            if (customHeaders.containsKey(name)) {
                return Collections.enumeration(Collections.singletonList(customHeaders.get(name)));
            }
            return super.getHeaders(name);
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            Set<String> names = new HashSet<>(customHeaders.keySet());
            Enumeration<String> originalNames = super.getHeaderNames();
            while (originalNames.hasMoreElements()) {
                names.add(originalNames.nextElement());
            }
            return Collections.enumeration(names);
        }
    }

    public static String getClientIp(HttpServletRequest request) {
        // 1. 优先从代理头获取
        String[] headerNames = {
                "X-Forwarded-For",   // 标准代理头
                "Proxy-Client-IP",    // Apache 代理
                "WL-Proxy-Client-IP", // WebLogic 代理
                "HTTP_X_FORWARDED_FOR",
                "HTTP_CLIENT_IP"
        };

        for (String header : headerNames) {
            String ip = request.getHeader(header);
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
                // X-Forwarded-For 可能有多个IP（client, proxy1, proxy2）
                if (ip.contains(",")) {
                    ip = ip.split(",")[0].trim(); // 取第一个IP
                }
                return ip;
            }
        }

        // 2. 没有代理头时直接获取
        return request.getRemoteAddr();
    }



}
