<?php

declare(strict_types=1);

namespace App\Framework\RateLimit;

use App\Framework\RateLimit\Storage\StorageInterface;
use App\Framework\RateLimit\TimeProvider\TimeProviderInterface;
use App\Framework\RateLimit\TimeProvider\SystemTimeProvider;

final readonly class RateLimiter
{
    public function __construct(
        private StorageInterface $storage,
        private TimeProviderInterface $timeProvider = new SystemTimeProvider()
    ) {}

    public function checkLimit(string $key, int $limit, int $window): RateLimitResult
    {
        $now = $this->timeProvider->getCurrentTime();
        $windowStart = $now - $window;

        // Sliding Window Log Implementierung
        $requests = $this->storage->getRequestsInWindow($key, $windowStart, $now);

        if (count($requests) >= $limit) {
            $oldestRequest = min($requests);
            $retryAfter = $oldestRequest + $window - $now;

            return RateLimitResult::exceeded($limit, count($requests), max(1, $retryAfter));
        }

        // Request hinzufügen
        $this->storage->addRequest($key, $now, $window);

        return RateLimitResult::allowed($limit, count($requests) + 1);
    }

    public function checkTokenBucket(string $key, int $capacity, int $refillRate, int $tokens = 1): RateLimitResult
    {
        $now = $this->timeProvider->getCurrentTime();
        $bucket = $this->storage->getTokenBucket($key);

        if ($bucket === null) {
            $bucket = new TokenBucket($capacity, $capacity, $now);
        }

        // Token nachfüllen
        $timePassed = $now - $bucket->lastRefill;
        $tokensToAdd = min($capacity - $bucket->tokens, intval($timePassed * $refillRate));

        $bucket = new TokenBucket(
            $bucket->capacity,
            $bucket->tokens + $tokensToAdd,
            $now
        );

        if ($bucket->tokens >= $tokens) {
            $bucket = new TokenBucket(
                $bucket->capacity,
                $bucket->tokens - $tokens,
                $bucket->lastRefill
            );

            $this->storage->saveTokenBucket($key, $bucket);

            return RateLimitResult::allowed($capacity, $bucket->tokens);
        }

        $this->storage->saveTokenBucket($key, $bucket);

        $retryAfter = intval(($tokens - $bucket->tokens) / $refillRate);
        return RateLimitResult::exceeded($capacity, $bucket->tokens, $retryAfter);
    }

    public function reset(string $key): void
    {
        $this->storage->clear($key);
    }

    public function getUsage(string $key, int $window): int
    {
        $now = $this->timeProvider->getCurrentTime();
        $windowStart = $now - $window;

        return count($this->storage->getRequestsInWindow($key, $windowStart, $now));
    }
}
