<?php

class RateLimiter {
    private $maxRequestsPerMinute = 60;
    private $maxRequestsPerHour = 1000;

    public function __construct($maxPerMinute = 60, $maxPerHour = 1000) {
        $this->maxRequestsPerMinute = $maxPerMinute;
        $this->maxRequestsPerHour = $maxPerHour;
    }

    public function checkRateLimit($identifier) {
        $now = time();
        $oneMinuteAgo = $now - 60;
        $oneHourAgo = $now - 3600;

        $minuteKey = "rate_limit_minute:$identifier";
        $hourKey = "rate_limit_hour:$identifier";

        // Obtener datos actuales
        $minuteData = $this->getRateData($minuteKey);
        $hourData = $this->getRateData($hourKey);

        // Limpiar peticiones viejas
        $minuteRequests = array_filter($minuteData, function($timestamp) use ($oneMinuteAgo) {
            return $timestamp >= $oneMinuteAgo;
        });

        $hourRequests = array_filter($hourData, function($timestamp) use ($oneHourAgo) {
            return $timestamp >= $oneHourAgo;
        });

        // Calcular peticiones restantes
        $remainingMinute = max(0, $this->maxRequestsPerMinute - count($minuteRequests));
        $remainingHour = max(0, $this->maxRequestsPerHour - count($hourRequests));

        // Si excede límite por minuto
        if (count($minuteRequests) >= $this->maxRequestsPerMinute) {
            $retryAfter = min($minuteRequests) + 60;
            $this->setRateLimitHeaders($remainingMinute, $this->maxRequestsPerMinute, $retryAfter - $now);
            throw new RateLimitException('Demasiadas peticiones. Máximo ' . $this->maxRequestsPerMinute . ' por minuto.', $retryAfter - $now);
        }

        // Si excede límite por hora
        if (count($hourRequests) >= $this->maxRequestsPerHour) {
            $retryAfter = min($hourRequests) + 3600;
            $this->setRateLimitHeaders($remainingHour, $this->maxRequestsPerHour, $retryAfter - $now);
            throw new RateLimitException('Demasiadas peticiones. Máximo ' . $this->maxRequestsPerHour . ' por hora.', $retryAfter - $now);
        }

        // Agregar petición actual
        $minuteRequests[] = $now;
        $hourRequests[] = $now;

        // Guardar datos actualizados
        $this->setRateData($minuteKey, $minuteRequests, 120);
        $this->setRateData($hourKey, $hourRequests, 3600);

        // Establecer headers de rate limit
        $this->setRateLimitHeaders($remainingMinute, $this->maxRequestsPerMinute, null);

        return true;
    }

    private function getRateData($key) {
        // Usar sesión para rate limiting
        if (session_status() === PHP_SESSION_ACTIVE && isset($_SESSION[$key])) {
            return $_SESSION[$key];
        }

        return [];
    }

    private function setRateData($key, $data, $ttl = null) {
        // Usar sesión para rate limiting
        if (session_status() === PHP_SESSION_ACTIVE) {
            $_SESSION[$key] = $data;
        }
    }

    private function setRateLimitHeaders($remaining, $limit, $retryAfter = null) {
        header("X-RateLimit-Limit: $limit");
        header("X-RateLimit-Remaining: $remaining");

        if ($retryAfter !== null) {
            header("X-RateLimit-Reset: $retryAfter");
            header("Retry-After: $retryAfter");
        }
    }

    public function getIdentifierFromRequest() {
        // Usar IP como identificador por defecto
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';

        // Si hay token JWT, usar user_id + IP para limitar por usuario
        $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';

        if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
            require_once __DIR__ . '/JWT.php';
            $token = $matches[1];
            $decoded = JWT::decode($token);

            if ($decoded && isset($decoded['user_id'])) {
                return 'user_' . $decoded['user_id'];
            }
        }

        return 'ip_' . $ip;
    }
}

class RateLimitException extends Exception {
    private $retryAfter;

    public function __construct($message, $retryAfter) {
        parent::__construct($message);
        $this->retryAfter = $retryAfter;
    }

    public function getRetryAfter() {
        return $this->retryAfter;
    }
}
