<?php

declare(strict_types=1);

namespace App\Framework\Http\RateLimiter;

use Predis\Client as RedisClient;

/**
 * Redis-basierte Implementierung eines Rate-Limiters.
 * Verwendet Redis Sorted Sets für präzises Sliding Window Rate Limiting.
 */
final readonly class RedisRateLimiter
{
    private const string KEY_PREFIX = 'rate_limit:';

    public function __construct(
        private RedisClient $redis
    ) {
    }

    /**
     * Prüft, ob eine Anfrage das Rate-Limit überschreitet.
     *
     * @param string $identifier Eindeutiger Identifikator (z.B. IP-Adresse)
     * @param string $action Optional: Name der Aktion (z.B. 'login', 'api')
     * @param int $limit Anzahl der erlaubten Anfragen im Zeitfenster
     * @param int $windowSeconds Länge des Zeitfensters in Sekunden
     * @return array{allowed: bool, current: int, remaining: int, reset: int} Ergebnis der Prüfung
     */
    public function check(
        string $identifier,
        string $action = 'default',
        int $limit = 60,
        int $windowSeconds = 60
    ): array {
        $key = $this->getKey($identifier, $action);
        $now = time();

        // Anfragen entfernen, die außerhalb des Zeitfensters liegen
        $this->redis->zremrangebyscore($key, 0, $now - $windowSeconds);

        // Aktuelle Anzahl der Anfragen im Zeitfenster
        $current = (int)$this->redis->zcard($key);

        // Wenn noch unter dem Limit, diese Anfrage hinzufügen
        if ($current < $limit) {
            $this->redis->zadd($key, [$now => $now]);
            $this->redis->expire($key, $windowSeconds);
            $current++;
        }

        // Zeitpunkt berechnen, wann das Limit zurückgesetzt wird
        $oldestRequest = $this->redis->zrange($key, 0, 0, ['WITHSCORES' => true]);
        $resetTime = 0;

        if (!empty($oldestRequest)) {
            $oldestTimestamp = (int)reset($oldestRequest);
            $resetTime = $oldestTimestamp + $windowSeconds;
        }

        return [
            'allowed' => $current <= $limit,
            'current' => $current,
            'remaining' => max(0, $limit - $current),
            'reset' => $resetTime - $now
        ];
    }

    /**
     * Erzeugt einen Reset des Rate-Limits für einen bestimmten Identifikator.
     * Nützlich z.B. nach erfolgreicher Authentifizierung.
     *
     * @param string $identifier Eindeutiger Identifikator
     * @param string $action Optional: Name der Aktion
     */
    public function reset(string $identifier, string $action = 'default'): void
    {
        $key = $this->getKey($identifier, $action);
        $this->redis->del($key);
    }

    /**
     * Erzeugt den Redis-Schlüssel für den gegebenen Identifikator und die Aktion.
     */
    private function getKey(string $identifier, string $action): string
    {
        return self::KEY_PREFIX . "{$identifier}:{$action}";
    }
}
