<?php
declare(strict_types=1);

namespace SafeTun\Backend\Storage;

final class RateLimiter
{
    private ShardedStore $store;

    public function __construct(string $root)
    {
        $this->store = new ShardedStore($root);
    }

    public function allow(string $bucket, int $limit, int $windowSeconds, ?int $now = null): bool
    {
        if ($limit < 1 || $windowSeconds < 1 || strlen($bucket) > 512) {
            throw new \InvalidArgumentException('Invalid rate-limit parameters.');
        }
        $now ??= time();
        $path = $this->store->pathFor('rate', $bucket);
        $lockPath = $this->store->lockPathFor('rate', $bucket);
        $lock = @fopen($lockPath, 'c+b');
        if (!is_resource($lock)) {
            throw new \RuntimeException('Unable to open rate-limit coordination lock.');
        }
        @chmod($lockPath, 0600);
        $handle = null;
        try {
            if (!flock($lock, LOCK_EX)) {
                throw new \RuntimeException('Unable to lock rate-limit state.');
            }
            $handle = @fopen($path, 'c+b');
        } catch (\Throwable $exception) {
            fclose($lock);
            throw $exception;
        }
        if (!is_resource($handle)) {
            flock($lock, LOCK_UN);
            fclose($lock);
            throw new \RuntimeException('Unable to open rate-limit state.');
        }
        try {
            $raw = SecureFiles::readBounded($handle);
            if ($raw === '') {
                $record = ['v' => 1, 'window_start' => $now, 'count' => 0];
            } else {
                $record = SecureFiles::decodeJson($raw);
                if (($record['v'] ?? null) !== 1 || !is_int($record['window_start'] ?? null)
                    || !is_int($record['count'] ?? null) || $record['count'] < 0) {
                    throw new \RuntimeException('Malformed rate-limit state.');
                }
            }
            if ($now < $record['window_start']) {
                throw new \RuntimeException('System clock moved backwards during a rate-limit window.');
            }
            if ($now - $record['window_start'] >= $windowSeconds) {
                $record = ['v' => 1, 'window_start' => $now, 'count' => 0];
            }
            $allowed = $record['count'] < $limit;
            if ($allowed) {
                $record['count']++;
                SecureFiles::replaceLocked($handle, SecureFiles::encodeJson($record));
                @chmod($path, 0600);
            }
            return $allowed;
        } finally {
            fclose($handle);
            flock($lock, LOCK_UN);
            fclose($lock);
        }
    }

    public function cleanup(int $olderThan, int $maximumFiles = 200): int
    {
        return $this->store->cleanup($olderThan, $maximumFiles);
    }
}
