package com.haha.common.interceptor;

import com.haha.common.config.LogProperties;
import com.haha.common.constant.LogConstants;
import com.haha.common.utils.SensitiveDataUtils;
import com.haha.common.utils.TraceIdUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class RequestLogInterceptor implements HandlerInterceptor {

    private final LogProperties logProperties;

    public RequestLogInterceptor(LogProperties logProperties) {
        this.logProperties = logProperties;
    }

    private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();

    private static final String START_TIME_ATTRIBUTE = "requestStartTime";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查是否为静态资源请求
        if (isStaticResource(request)) {
            return true;
        }

        // 生成 TraceId 并设置到 MDC
        String traceId = TraceIdUtils.getOrGenerateTraceId();
        response.setHeader(LogConstants.TRACE_ID_KEY, traceId);

        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        START_TIME.set(startTime);
        request.setAttribute(START_TIME_ATTRIBUTE, startTime);

        // 获取请求信息
        String method = request.getMethod();
        String requestUri = request.getRequestURI();
        String queryString = request.getQueryString();
        String clientIp = getClientIp(request);
        String userAgent = request.getHeader("User-Agent");

        // 获取请求参数并脱敏
        Map<String, String[]> parameterMap = request.getParameterMap();
        String params = maskParameters(parameterMap);

        // 记录请求开始日志
        log.info("[请求开始] TraceId: {}, 方法: {}, 路径: {}, 参数: {}, IP: {}, User-Agent: {}",
                traceId, method, requestUri + (queryString != null ? "?" + queryString : ""),
                params, clientIp, userAgent);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try {
            // 检查是否为静态资源请求
            if (isStaticResource(request)) {
                return;
            }

            // 获取 TraceId
            String traceId = TraceIdUtils.getTraceId();

            // 计算请求处理时长
            Long startTime = START_TIME.get();
            long costTime = 0L;
            if (startTime != null) {
                costTime = System.currentTimeMillis() - startTime;
            }

            // 获取响应状态
            int status = response.getStatus();

            // 获取请求信息
            String method = request.getMethod();
            String requestUri = request.getRequestURI();

            // 判断是否为慢请求
            long slowRequestThreshold = logProperties.getSlowRequestThreshold() != null 
                ? logProperties.getSlowRequestThreshold() 
                : LogConstants.DEFAULT_SLOW_REQUEST_THRESHOLD;
            if (costTime > slowRequestThreshold) {
                log.warn("[请求结束-慢请求] TraceId: {}, 方法: {}, 路径: {}, 状态: {}, 耗时: {}ms",
                        traceId, method, requestUri, status, costTime);
            } else {
                log.info("[请求结束] TraceId: {}, 方法: {}, 路径: {}, 状态: {}, 耗时: {}ms",
                        traceId, method, requestUri, status, costTime);
            }

            // 如果有异常，记录异常信息
            if (ex != null) {
                log.error("[请求异常] TraceId: {}, 方法: {}, 路径: {}, 异常: {}",
                        traceId, method, requestUri, ex.getMessage(), ex);
            }
        } finally {
            // 清理 ThreadLocal
            START_TIME.remove();

            // 清理 MDC 中的 TraceId
            TraceIdUtils.removeTraceId();
        }
    }

    /**
     * 判断是否为静态资源请求
     *
     * @param request HTTP 请求
     * @return 是否为静态资源
     */
    private boolean isStaticResource(HttpServletRequest request) {
        String requestUri = request.getRequestURI();
        if (requestUri == null) {
            return false;
        }

        // 检查文件扩展名
        String[] staticExtensions = LogConstants.STATIC_RESOURCE_EXTENSIONS;
        for (String extension : staticExtensions) {
            if (requestUri.toLowerCase().endsWith(extension.toLowerCase())) {
                return true;
            }
        }

        // 检查排除路径
        String[] excludePaths = LogConstants.EXCLUDE_PATHS;
        for (String excludePath : excludePaths) {
            if (excludePath.endsWith("/**")) {
                String basePath = excludePath.substring(0, excludePath.length() - 3);
                if (requestUri.startsWith(basePath)) {
                    return true;
                }
            } else if (requestUri.equals(excludePath)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 获取客户端真实 IP 地址
     *
     * @param request HTTP 请求
     * @return 客户端 IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 对于多个代理的情况，第一个 IP 为客户端真实 IP
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }

    /**
     * 对请求参数进行脱敏处理
     *
     * @param parameterMap 请求参数 Map
     * @return 脱敏后的参数字符串
     */
    private String maskParameters(Map<String, String[]> parameterMap) {
        if (parameterMap == null || parameterMap.isEmpty()) {
            return "{}";
        }

        Map<String, Object> maskedParams = new HashMap<>();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();

            if (values == null || values.length == 0) {
                maskedParams.put(key, "");
            } else if (values.length == 1) {
                String value = values[0];
                if (isSensitiveParam(key) && Boolean.TRUE.equals(logProperties.getEnableSensitiveMask())) {
                    value = SensitiveDataUtils.mask(value);
                }
                if (value != null && value.length() > LogConstants.MAX_PARAM_LENGTH) {
                    value = value.substring(0, LogConstants.MAX_PARAM_LENGTH) + "...";
                }
                maskedParams.put(key, value);
            } else {
                String value = String.join(",", values);
                if (isSensitiveParam(key) && Boolean.TRUE.equals(logProperties.getEnableSensitiveMask())) {
                    value = SensitiveDataUtils.mask(value);
                }
                if (value != null && value.length() > LogConstants.MAX_PARAM_LENGTH) {
                    value = value.substring(0, LogConstants.MAX_PARAM_LENGTH) + "...";
                }
                maskedParams.put(key, value);
            }
        }

        return maskedParams.toString();
    }

    /**
     * 判断参数名是否为敏感参数
     *
     * @param paramName 参数名
     * @return 是否为敏感参数
     */
    private boolean isSensitiveParam(String paramName) {
        if (paramName == null) {
            return false;
        }
        String lowerParamName = paramName.toLowerCase();
        return lowerParamName.contains("password")
                || lowerParamName.contains("pwd")
                || lowerParamName.contains("passwd")
                || lowerParamName.contains("secret")
                || lowerParamName.contains("token")
                || lowerParamName.contains("key")
                || lowerParamName.contains("phone")
                || lowerParamName.contains("mobile")
                || lowerParamName.contains("idcard")
                || lowerParamName.contains("id_card")
                || lowerParamName.contains("bankcard")
                || lowerParamName.contains("bank_card");
    }
}
