package com.test.bijihoudaun.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.bijihoudaun.common.response.R;
import com.test.bijihoudaun.common.response.ResultCode;
import com.test.bijihoudaun.util.MemoryProtector;
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.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 防重放攻击拦截器
 * 通过时间戳和 nonce 机制防止请求被截获重放，带容量限制
 */
@Slf4j
public class ReplayAttackInterceptor implements HandlerInterceptor {

    // 请求时间戳有效期（毫秒）：10分钟，兼容移动端网络抖动与时钟偏差
    private static final long TIMESTAMP_VALIDITY = 10 * 60 * 1000;
    // 允许少量未来时间偏移（毫秒）：2分钟，兼容秒级时间戳和终端时钟轻微超前
    private static final long ALLOWED_FUTURE_DRIFT = 2 * 60 * 1000;
    // nonce 有效期（毫秒）：15分钟
    private static final long NONCE_EXPIRE_TIME = 15 * 60 * 1000;
    // 最大存储 nonce 数（防止内存溢出）
    private static final int MAX_NONCES = 100000;

    // 用于不需要验证的路径
    private static final Set<String> EXCLUDE_PATHS = new HashSet<>(Arrays.asList(
            "/api/user/login",
            "/api/user/register",
            "/api/system/registration/status"
    ));

    // 存储已使用的 nonce：key=nonce，value=使用时间戳
    private static final LRUCache<String, Long> usedNonces = new LRUCache<>(MAX_NONCES);
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 简单的 LRU 缓存实现
     */
    private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int maxSize;

        LRUCache(int maxSize) {
            super(maxSize, 0.75f, true);
            this.maxSize = maxSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            boolean shouldRemove = size() > maxSize;
            if (shouldRemove) {
                log.debug("nonce 存储达到上限，移除最旧的记录");
            }
            return shouldRemove;
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 检查内存状态
        if (MemoryProtector.isMemoryInsufficient()) {
            log.warn("内存不足，拒绝请求: {}", request.getRequestURI());
            MemoryProtector.writeMemoryInsufficientResponse(response);
            return false;
        }

        String requestUri = request.getRequestURI();

        // 排除不需要验证的路径
        if (EXCLUDE_PATHS.contains(requestUri)) {
            return true;
        }

        // GET 请求不需要验证（幂等操作）
        if ("GET".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 清理过期的 nonce
        cleanupExpiredNonces();

        // 获取请求头中的时间戳和 nonce
        String timestampStr = request.getHeader("X-Timestamp");
        String nonce = request.getHeader("X-Nonce");

        String userAgent = request.getHeader("User-Agent");

        // 检查参数是否存在
        if (timestampStr == null || timestampStr.isEmpty() || nonce == null || nonce.isEmpty()) {
            String missingHeader = (timestampStr == null || timestampStr.isEmpty()) && (nonce == null || nonce.isEmpty())
                    ? "X-Timestamp,X-Nonce"
                    : ((timestampStr == null || timestampStr.isEmpty()) ? "X-Timestamp" : "X-Nonce");
            log.warn("防重放校验失败: uri={}, method={}, ua={}, missingHeaders={}",
                    requestUri, request.getMethod(), userAgent, missingHeader);
            writeErrorResponse(response, "缺少安全验证参数");
            return false;
        }

        // 验证时间戳（兼容 10 位秒级与 13 位毫秒级）
        long timestamp;
        try {
            timestamp = normalizeTimestamp(timestampStr);
        } catch (NumberFormatException e) {
            log.warn("防重放校验失败: uri={}, method={}, ua={}, invalidTimestamp={}, error={}",
                    requestUri, request.getMethod(), userAgent, timestampStr, e.getMessage());
            writeErrorResponse(response, "无效的时间戳参数");
            return false;
        }

        long now = System.currentTimeMillis();
        long timeDiff = now - timestamp;
        if (timeDiff < -ALLOWED_FUTURE_DRIFT || timeDiff > TIMESTAMP_VALIDITY) {
            log.warn("防重放校验失败: uri={}, method={}, ua={}, timestamp={}, normalizedTimestamp={}, now={}, diffMs={}, validityMs={}",
                    requestUri, request.getMethod(), userAgent, timestampStr, timestamp, now, timeDiff, TIMESTAMP_VALIDITY);
            writeErrorResponse(response, "请求时间戳无效或已过期");
            return false;
        }

        // 验证 nonce 是否已被使用
        lock.readLock().lock();
        try {
            if (usedNonces.containsKey(nonce)) {
                log.warn("防重放校验失败: uri={}, method={}, ua={}, nonce={}, reason=reused_nonce",
                        requestUri, request.getMethod(), userAgent, nonce);
                writeErrorResponse(response, "检测到重放攻击，请求已被拒绝");
                return false;
            }
        } finally {
            lock.readLock().unlock();
        }

        // 记录 nonce
        lock.writeLock().lock();
        try {
            usedNonces.put(nonce, now);
        } finally {
            lock.writeLock().unlock();
        }

        return true;
    }


    private long normalizeTimestamp(String timestampStr) {
        long parsedTimestamp = Long.parseLong(timestampStr);
        if (timestampStr.length() == 10) {
            return parsedTimestamp * 1000;
        }
        return parsedTimestamp;
    }

    /**
     * 清理过期的 nonce
     */
    private void cleanupExpiredNonces() {
        // 每80%容量时清理一次，减少开销
        if (usedNonces.size() < MAX_NONCES * 0.8) {
            return;
        }

        lock.writeLock().lock();
        try {
            long now = System.currentTimeMillis();
            usedNonces.entrySet().removeIf(entry ->
                    (now - entry.getValue()) > NONCE_EXPIRE_TIME
            );
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 写入错误响应
     */
    private void writeErrorResponse(HttpServletResponse response, String message) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(400);
        ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(
                R.fail(ResultCode.FAILED.getCode(), message)
        ));
    }
}
