<?php
declare(strict_types=1);

namespace App\Security;

use Redis;
use Throwable;

/**
 * Redis-backed limiter for multi-instance deployments.
 */
final class RedisRateLimiter implements RateLimiterInterface
{
    private Redis $redis;
    private string $prefix;

    public function __construct(array $config = [])
    {
        if (!class_exists(Redis::class)) {
            throw new \RuntimeException('Redis extension is not available');
        }

        $host = trim((string)($config['host'] ?? '127.0.0.1'));
        $port = (int)($config['port'] ?? 6379);
        $timeout = (float)($config['timeout'] ?? 1.5);
        $password = (string)($config['password'] ?? '');
        $database = (int)($config['database'] ?? 0);
        $this->prefix = trim((string)($config['prefix'] ?? 'ly:ratelimit:'));
        if ($this->prefix === '') {
            $this->prefix = 'ly:ratelimit:';
        }

        $redis = new Redis();
        $redis->connect($host, $port, $timeout);
        if ($password !== '') {
            $redis->auth($password);
        }
        if ($database > 0) {
            $redis->select($database);
        }

        $this->redis = $redis;
    }

    public static function isAvailable(array $config = []): bool
    {
        if (!class_exists(Redis::class)) {
            return false;
        }

        try {
            new self($config);
            return true;
        } catch (Throwable $e) {
            return false;
        }
    }

    public function consume(string $scope, int $limit, int $windowSec, string $identity = ''): array
    {
        $scope = trim($scope);
        if ($scope === '' || $limit < 1 || $windowSec < 1) {
            return ['limited' => false, 'remaining' => max(0, $limit), 'retry_after' => 0];
        }

        $identity = trim($identity);
        if ($identity === '') {
            $identity = 'global';
        }

        $now = time();
        $bucket = (int)floor($now / $windowSec);
        $expiresAt = ($bucket + 1) * $windowSec;
        $key = $this->prefix . hash('sha256', $scope . '|' . $identity . '|' . $bucket);

        try {
            $count = (int)$this->redis->incr($key);
            if ($count === 1) {
                $this->redis->expireAt($key, $expiresAt);
            }
        } catch (Throwable $e) {
            return [
                'limited' => false,
                'remaining' => max(0, $limit - 1),
                'retry_after' => max(0, $expiresAt - $now),
            ];
        }

        if ($count > $limit) {
            return [
                'limited' => true,
                'remaining' => 0,
                'retry_after' => max(1, $expiresAt - $now),
            ];
        }

        return [
            'limited' => false,
            'remaining' => max(0, $limit - $count),
            'retry_after' => max(0, $expiresAt - $now),
        ];
    }
}
