package com.mosquito.project.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 回调风控服务
 * PRD要求：防刷 - 来源IP白名单 + 设备指纹校验
 */
@Service
public class CallbackRiskGuardService {

    private static final Logger log = LoggerFactory.getLogger(CallbackRiskGuardService.class);

    // 白名单IP列表（配置化）
    @Value("${mosquito.callback.whitelist.ips:}")
    private String whitelistIps;

    // 设备指纹必需标记
    @Value("${mosquito.callback.require.device.fingerprint:true}")
    private boolean requireDeviceFingerprint;

    // 单IP请求上限（配置化）
    @Value("${mosquito.callback.rate-limit.ip.max-requests:100}")
    private int ipMaxRequests;

    // 时间窗口（秒）
    @Value("${mosquito.callback.rate-limit.ip.window-seconds:60}")
    private int ipWindowSeconds;

    // 是否启用Redis限流（生产环境建议开启）
    @Value("${mosquito.callback.rate-limit.use-redis:true}")
    private boolean useRedisRateLimit;

    // 是否允许本地回环地址（测试环境开启）
    @Value("${mosquito.callback.allow-localhost:false}")
    private boolean allowLocalhost;

    // 白名单宽松模式（生产环境应关闭，仅dev环境开启）
    @Value("${mosquito.callback.whitelist.permissive:false}")
    private boolean whitelistPermissive;

    // Redis key前缀
    private static final String RATE_LIMIT_KEY_PREFIX = "mosquito:ratelimit:ip:";

    // StringRedisTemplate（可选注入，如果不可用则使用内存限流）
    private StringRedisTemplate redisTemplate;

    // 内存缓存：IP -> 请求计数（Redis不可用时备用）
    private final Map<String, IpRequestCounter> ipRequestCache = new ConcurrentHashMap<>();

    public CallbackRiskGuardService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @PostConstruct
    public void init() {
        // 生产环境 fail-fast：非 permissive 模式下白名单不能为空
        if (!whitelistPermissive && (whitelistIps == null || whitelistIps.isBlank())) {
            String errorMsg = "生产环境回调白名单配置缺失！请配置 mosquito.callback.whitelist.ips 或启用 permissive 模式。";
            log.error(errorMsg);
            throw new IllegalStateException(errorMsg);
        }

        log.info("回调风控服务初始化完成: whitelistIps={}, requireDeviceFingerprint={}, ipMaxRequests={}, ipWindowSeconds={}, useRedisRateLimit={}, allowLocalhost={}, whitelistPermissive={}",
                whitelistIps, requireDeviceFingerprint, ipMaxRequests, ipWindowSeconds, useRedisRateLimit, allowLocalhost, whitelistPermissive);
    }

    /**
     * 校验请求风控
     * @param ip 来源IP
     * @param deviceFingerprint 设备指纹
     * @return 风控结果
     */
    public RiskCheckResult checkRisk(String ip, String deviceFingerprint) {
        // 1. IP白名单校验
        if (!isIpWhitelisted(ip)) {
            log.warn("风控拒绝: IP {} 不在白名单中", ip);
            return new RiskCheckResult(false, "IP_NOT_WHITELISTED", "来源IP不在白名单中");
        }

        // 2. 设备指纹校验
        if (requireDeviceFingerprint && (deviceFingerprint == null || deviceFingerprint.isBlank())) {
            log.warn("风控拒绝: 缺少设备指纹, IP={}", ip);
            return new RiskCheckResult(false, "MISSING_DEVICE_FINGERPRINT", "缺少设备指纹");
        }

        // 3. IP请求频率校验
        if (!checkIpRateLimit(ip)) {
            log.warn("风控拒绝: IP {} 请求频率超限", ip);
            return new RiskCheckResult(false, "IP_RATE_LIMIT_EXCEEDED", "请求频率超限");
        }

        return new RiskCheckResult(true, "PASSED", "风控校验通过");
    }

    /**
     * IP是否在白名单中
     * PRD语义：生产环境应默认fail-closed（白名单为空时拒绝请求）
     */
    private boolean isIpWhitelisted(String ip) {
        // 允许本地回环地址（如果配置开启）
        if (allowLocalhost && isLocalhost(ip)) {
            return true;
        }

        // 白名单为空时的处理策略
        if (whitelistIps == null || whitelistIps.isBlank()) {
            // 生产环境(fail-closed): 白名单未配置时拒绝请求
            if (!whitelistPermissive) {
                log.warn("白名单未配置且未开启宽松模式，拒绝IP: {}", ip);
                return false;
            }
            // 开发环境(宽松模式): 仅验证IP格式有效性
            log.warn("白名单未配置但开启了宽松模式，仅验证IP格式: {}", ip);
            return isValidIp(ip);
        }

        List<String> whitelist = Arrays.asList(whitelistIps.split(","));
        return whitelist.stream()
                .map(String::trim)
                .anyMatch(allowed -> matchIp(allowed, ip));
    }

    /**
     * IP匹配（支持通配符和CIDR）
     */
    private boolean matchIp(String pattern, String ip) {
        if (pattern.contains("/")) {
            // CIDR格式，如 192.168.0.0/16
            return matchesCidr(pattern, ip);
        } else if (pattern.contains("*")) {
            // 通配符，如 192.168.*
            String prefix = pattern.substring(0, pattern.indexOf("*"));
            return ip.startsWith(prefix);
        }
        return pattern.equals(ip);
    }

    /**
     * CIDR匹配
     * 使用标准位运算实现，支持 /0 到 /32 的所有CIDR前缀
     */
    private boolean matchesCidr(String cidr, String ip) {
        try {
            String[] parts = cidr.split("/");
            if (parts.length != 2) {
                log.warn("CIDR格式无效: {}", cidr);
                return false;
            }
            String baseIpStr = parts[0];
            int prefix = Integer.parseInt(parts[1]);

            // 验证prefix范围
            if (prefix < 0 || prefix > 32) {
                log.warn("CIDR prefix无效: {}", prefix);
                return false;
            }

            // 验证IP格式
            if (!isValidIpV4(baseIpStr) || !isValidIpV4(ip)) {
                return false;
            }

            // 将IP转换为32位整数
            long ipLong = ipToLong(ip);
            long baseIpLong = ipToLong(baseIpStr);

            // 计算子网掩码
            long mask = (0xFFFFFFFFL << (32 - prefix)) & 0xFFFFFFFFL;

            // 比较网络地址是否相等
            return (ipLong & mask) == (baseIpLong & mask);
        } catch (Exception e) {
            log.warn("CIDR解析失败: {}", cidr, e);
            return false;
        }
    }

    /**
     * 将IPv4字符串转换为32位整数
     */
    private long ipToLong(String ipStr) {
        String[] parts = ipStr.split("\\.");
        if (parts.length != 4) {
            throw new IllegalArgumentException("无效的IPv4地址: " + ipStr);
        }
        long result = 0;
        for (int i = 0; i < 4; i++) {
            int octet = Integer.parseInt(parts[i]);
            if (octet < 0 || octet > 255) {
                throw new IllegalArgumentException("无效的IPv4地址: " + ipStr);
            }
            result = (result << 8) | octet;
        }
        return result;
    }

    /**
     * 验证IPv4地址格式
     */
    private boolean isValidIpV4(String ip) {
        if (ip == null || ip.isBlank()) {
            return false;
        }
        String[] parts = ip.split("\\.");
        if (parts.length != 4) {
            return false;
        }
        try {
            for (String part : parts) {
                int octet = Integer.parseInt(part);
                if (octet < 0 || octet > 255) {
                    return false;
                }
            }
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * 判断是否为本地回环地址
     */
    private boolean isLocalhost(String ip) {
        return "127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip) ||
               "::1".equals(ip) || "localhost".equalsIgnoreCase(ip);
    }

    /**
     * 校验IP格式
     */
    private boolean isValidIp(String ip) {
        if (ip == null || ip.isBlank()) {
            return false;
        }
        // 简单校验：不为localhost且格式合理
        if (allowLocalhost && isLocalhost(ip)) {
            return true;
        }
        return !ip.equals("127.0.0.1") && !ip.equals("0.0.0.0") && !ip.equals("localhost");
    }

    /**
     * IP请求频率校验
     */
    private boolean checkIpRateLimit(String ip) {
        if (useRedisRateLimit && redisTemplate != null) {
            return checkIpRateLimitRedis(ip);
        } else {
            return checkIpRateLimitMemory(ip);
        }
    }

    /**
     * Redis限流检查
     */
    private boolean checkIpRateLimitRedis(String ip) {
        try {
            String key = RATE_LIMIT_KEY_PREFIX + ip;
            Long count = redisTemplate.opsForValue().increment(key);
            if (count != null && count == 1) {
                // 首次请求，设置过期时间
                redisTemplate.expire(key, Duration.ofSeconds(ipWindowSeconds));
            }
            if (count != null && count > ipMaxRequests) {
                log.warn("Redis限流: IP {} 请求次数 {} 超过限制 {}", ip, count, ipMaxRequests);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.warn("Redis限流失败，切换到内存限流: {}", e.getMessage());
            return checkIpRateLimitMemory(ip);
        }
    }

    /**
     * 内存限流检查（备用方案）
     */
    private synchronized boolean checkIpRateLimitMemory(String ip) {
        IpRequestCounter counter = ipRequestCache.computeIfAbsent(ip, k -> new IpRequestCounter());

        long now = System.currentTimeMillis();
        long windowStart = now - (ipWindowSeconds * 1000L);

        // 清理过期记录
        counter.cleanOldRecords(windowStart);

        // 检查是否超限
        if (counter.getCount() >= ipMaxRequests) {
            return false;
        }

        // 增加计数
        counter.addRequest(now);
        return true;
    }

    /**
     * 风控结果
     */
    public static class RiskCheckResult {
        private final boolean passed;
        private final String code;
        private final String message;

        public RiskCheckResult(boolean passed, String code, String message) {
            this.passed = passed;
            this.code = code;
            this.message = message;
        }

        public boolean isPassed() { return passed; }
        public String getCode() { return code; }
        public String getMessage() { return message; }
    }

    /**
     * IP请求计数器
     */
    private static class IpRequestCounter {
        private long count = 0;
        private long windowStart = 0;

        public synchronized void addRequest(long timestamp) {
            count++;
            windowStart = timestamp;
        }

        public synchronized long getCount() {
            return count;
        }

        public synchronized void cleanOldRecords(long windowStart) {
            // 简单实现：每次窗口期重置
            if (this.windowStart < windowStart) {
                count = 0;
            }
        }
    }
}
