package com.njzscloud.common.security.support;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.njzscloud.common.security.util.SecurityUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.njzscloud.common.security.contant.Constants.ANONYMOUS_USER;
import static com.njzscloud.common.security.contant.Constants.LOGIN_URL;

/**
 * TOKEN 存取器
 */
@Slf4j
public class TokenSecurityContextRepository implements SecurityContextRepository {

    /**
     * 请求头 TOKEN 匹配正则
     */
    private static final Pattern AUTHORIZATION_PATTERN = Pattern.compile("^(?<token>[a-zA-Z0-9-.:_~+/]+=*)$", Pattern.CASE_INSENSITIVE);

    /**
     * Websocket TOKEN 所在的请求头
     */
    private static final String SEC_WEB_SOCKET_PROTOCOL = "Sec-WebSocket-Protocol";

    /**
     * 解析 TOKEN
     *
     * @param requestResponseHolder 请求信息存储器
     * @return 认证信息
     */
    @Override
    @SuppressWarnings("deprecation")
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {

        HttpServletRequest request = requestResponseHolder.getRequest();
        // 普通请求头中的 TOKEN
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);

        // 从 Query 参数中获取 TOKEN
        if (StrUtil.isBlank(authorization)) {
            String queryString = request.getQueryString();
            if (StrUtil.isNotBlank(queryString)) {
                String[] split = queryString.split("&");
                for (String s : split) {
                    int idx = s.indexOf('=');
                    String key = idx > 0 ? URLUtil.decode(s.substring(0, idx), "UTF-8") : s;
                    if (HttpHeaders.AUTHORIZATION.equalsIgnoreCase(key)) {
                        authorization = idx > 0 && s.length() > idx + 1 ? URLUtil.decode(s.substring(idx + 1), "UTF-8") : null;
                    }
                }
            }
        }

        // 从 Websocket 子协议请求头中获取 TOKEN
        if (StrUtil.isBlank(authorization)) {
            String websocketHeader = request.getHeader(SEC_WEB_SOCKET_PROTOCOL);
            if (StrUtil.isNotBlank(websocketHeader)) {
                authorization = websocketHeader.split(",")[0].trim();
            }
        }

        UserDetail userDetail = null;

        if (StrUtil.isNotBlank(authorization)) {
            authorization = URLUtil.decode(authorization);
            Matcher matcher = AUTHORIZATION_PATTERN.matcher(authorization);
            if (matcher.matches()) {
                String tokenStr = matcher.group("token");
                try {
                    userDetail = SecurityUtil.parseToken(tokenStr);
                } catch (Exception e) {
                    log.warn("TOKEN 解析失败", e);
                }
            }
        }

        if (userDetail == null || userDetail.getToken().isExpired()) {
            userDetail = ANONYMOUS_USER;
        }

        SecurityContext context = SecurityContextHolder.createEmptyContext();
        AuthenticationDetails details = AuthenticationDetails.create(request);
        context.setAuthentication(UserAuthenticationToken.create(userDetail, details));
        return context;
    }

    /**
     * 保存 TOKEN
     *
     * @param context  认证信息
     * @param request  请求对象
     * @param response 响应对象
     */
    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        Authentication authentication = context.getAuthentication();
        // 退出登录时会为 null
        if (!request.getRequestURI().startsWith(LOGIN_URL) || authentication == null) return;
        UserAuthenticationToken userAuthenticationToken = (UserAuthenticationToken) authentication;
        UserDetail userDetail = (UserDetail) userAuthenticationToken.getPrincipal();
        SecurityUtil.saveToken(userDetail);
    }

    /**
     * 始终为 true
     */
    @Override
    public boolean containsContext(HttpServletRequest request) {
        return true;
    }
}
