<?php
/**
 * HuSNS - 请求限流中间件
 * 
 * @author  HYR
 * @QQ      281900864
 * @website https://huyourui.com
 * @license MIT
 */

class RateLimiter
{
    /**
     * 默认限流配置
     */
    const DEFAULT_LIMIT = 60;        // 默认每分钟60次
    const DEFAULT_WINDOW = 60;        // 时间窗口60秒
    
    /**
     * 限流配置（延迟初始化，避免常量表达式错误）
     */
    private static $config = null;

    /**
     * 初始化限流配置
     */
    private static function initConfig(): void
    {
        if (self::$config === null) {
            self::$config = [
                // 全局限流（每IP）
                'global' => [
                    'limit' => defined('RATE_LIMIT_GLOBAL') ? RATE_LIMIT_GLOBAL : 100,
                    'window' => 60,
                ],
                // API限流（每IP）
                'api' => [
                    'limit' => defined('RATE_LIMIT_API') ? RATE_LIMIT_API : 30,
                    'window' => 60,
                ],
                // 登录限流（每IP）
                'login' => [
                    'limit' => defined('RATE_LIMIT_LOGIN') ? RATE_LIMIT_LOGIN : 5,
                    'window' => defined('ADMIN_VERIFY_TIMEOUT') ? ADMIN_VERIFY_TIMEOUT : 300,  // 5分钟
                ],
                // 注册限流（每IP）
                'register' => [
                    'limit' => defined('RATE_LIMIT_REGISTER') ? RATE_LIMIT_REGISTER : 3,
                    'window' => 3600, // 1小时
                ],
                // 验证码请求限流（每IP）
                'sms' => [
                    'limit' => defined('RATE_LIMIT_SMS') ? RATE_LIMIT_SMS : 5,
                    'window' => 3600, // 1小时
                ],
            ];
        }
    }

    /**
     * 获取配置
     */
    private static function getConfig(string $type): array
    {
        self::initConfig();
        return self::$config[$type] ?? self::$config['global'];
    }

    /**
     * 获取客户端IP
     */
    private static function getClientIp(): string
    {
        $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
        
        // 考虑代理情况
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $ip = trim($ips[0]);
        } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
            $ip = $_SERVER['HTTP_X_REAL_IP'];
        }
        
        return $ip;
    }

    /**
     * 获取限流存储文件路径
     */
    private static function getStorageFile(string $type): string
    {
        $cacheDir = defined('ROOT_PATH') ? ROOT_PATH . 'cache/' : __DIR__ . '/../cache/';
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
        return $cacheDir . 'rate_limit_' . md5($type) . '.json';
    }

    /**
     * 获取当前时间戳
     */
    private static function getCurrentTime(): int
    {
        return time();
    }

    /**
     * 检查请求是否超过限流
     * 
     * @param string $type 限流类型：global, api, login, register, sms
     * @param string|null $identifier 自定义标识符（如用户ID）
     * @return array ['allowed' => bool, 'remaining' => int, 'reset' => int]
     */
    public static function check(string $type = 'global', ?string $identifier = null): array
    {
        $config = self::getConfig($type);
        $limit = $config['limit'];
        $window = $config['window'];
        
        $ip = self::getClientIp();
        $key = $identifier ? "{$ip}:{$identifier}" : $ip;
        
        $storageFile = self::getStorageFile($type . '_' . md5($key));
        $data = self::loadData($storageFile);
        
        $currentTime = self::getCurrentTime();
        $windowStart = $currentTime - $window;
        
        // 清理过期的记录
        $data['requests'] = array_filter($data['requests'], function($timestamp) use ($windowStart) {
            return $timestamp > $windowStart;
        });
        
        // 计算当前请求次数
        $requestCount = count($data['requests']);
        
        // 检查是否超限
        if ($requestCount >= $limit) {
            $resetTime = !empty($data['requests']) ? min($data['requests']) + $window : $currentTime + $window;
            return [
                'allowed' => false,
                'remaining' => 0,
                'reset' => $resetTime,
                'limit' => $limit,
                'retry_after' => max(1, $resetTime - $currentTime),
            ];
        }
        
        // 记录本次请求
        $data['requests'][] = $currentTime;
        self::saveData($storageFile, $data);
        
        return [
            'allowed' => true,
            'remaining' => max(0, $limit - $requestCount - 1),
            'reset' => $currentTime + $window,
            'limit' => $limit,
        ];
    }

    /**
     * 记录一次请求（不检查）
     */
    public static function hit(string $type = 'global', ?string $identifier = null): bool
    {
        $result = self::check($type, $identifier);
        return $result['allowed'];
    }

    /**
     * 重置限流计数
     */
    public static function reset(string $type, ?string $identifier = null): bool
    {
        $ip = self::getClientIp();
        $key = $identifier ? "{$ip}:{$identifier}" : $ip;
        
        $storageFile = self::getStorageFile($type . '_' . md5($key));
        
        if (file_exists($storageFile)) {
            return unlink($storageFile);
        }
        
        return true;
    }

    /**
     * 获取限流配置的剩余时间
     */
    public static function getRemainingTime(string $type, ?string $identifier = null): int
    {
        $ip = self::getClientIp();
        $key = $identifier ? "{$ip}:{$identifier}" : $ip;
        
        $storageFile = self::getStorageFile($type . '_' . md5($key));
        $data = self::loadData($storageFile);
        
        if (empty($data['requests'])) {
            return 0;
        }
        
        $config = self::getConfig($type);
        $window = $config['window'];
        $oldestRequest = min($data['requests']);
        
        return max(0, $oldestRequest + $window - self::getCurrentTime());
    }

    /**
     * 加载限流数据
     */
    private static function loadData(string $file): array
    {
        if (file_exists($file)) {
            $content = file_get_contents($file);
            $data = json_decode($content, true);
            if (is_array($data)) {
                return $data;
            }
        }
        return ['requests' => []];
    }

    /**
     * 保存限流数据
     */
    private static function saveData(string $file, array $data): void
    {
        file_put_contents($file, json_encode($data), LOCK_EX);
    }

    /**
     * 清理所有限流数据
     */
    public static function clearAll(): bool
    {
        $cacheDir = defined('ROOT_PATH') ? ROOT_PATH . 'cache/' : __DIR__ . '/../cache/';
        
        if (!is_dir($cacheDir)) {
            return true;
        }
        
        $files = glob($cacheDir . 'rate_limit_*.json');
        $result = true;
        
        foreach ($files as $file) {
            if (!unlink($file)) {
                $result = false;
            }
        }
        
        return $result;
    }

    /**
     * 设置自定义限流配置
     */
    public static function setConfig(string $type, int $limit, int $window): void
    {
        self::$config[$type] = [
            'limit' => $limit,
            'window' => $window,
        ];
    }

    /**
     * 获取当前限流状态
     */
    public static function status(string $type, ?string $identifier = null): array
    {
        $ip = self::getClientIp();
        $key = $identifier ? "{$ip}:{$identifier}" : $ip;
        
        $storageFile = self::getStorageFile($type . '_' . md5($key));
        $data = self::loadData($storageFile);
        
        $config = self::getConfig($type);
        $window = $config['window'];
        $currentTime = self::getCurrentTime();
        $windowStart = $currentTime - $window;
        
        // 清理过期记录并计数
        $validRequests = array_filter($data['requests'], function($timestamp) use ($windowStart) {
            return $timestamp > $windowStart;
        });
        
        $requestCount = count($validRequests);
        
        return [
            'ip' => $ip,
            'type' => $type,
            'count' => $requestCount,
            'limit' => $config['limit'],
            'remaining' => max(0, $config['limit'] - $requestCount),
            'window' => $window,
            'reset' => $currentTime + $window,
        ];
    }
}
