<?php
// =============================================================
//  include/rate_limiter.php
//
//  Minimal file-based sliding-window rate limiter. Deliberately not
//  DB-backed — this needs to work the moment the code is deployed,
//  with no migration step required on the production server.
// =============================================================

if (!function_exists('rateLimitAttempt')) {
    /**
     * Records one attempt for $key and reports whether the caller has
     * exceeded $maxAttempts within the last $windowSeconds.
     *
     * @return array{blocked: bool, retry_after: int}
     */
    function rateLimitAttempt(string $key, int $maxAttempts, int $windowSeconds): array
    {
        $dir = __DIR__ . '/../storage/rate_limits';
        if (!is_dir($dir)) @mkdir($dir, 0755, true);
        $file = $dir . '/' . hash('sha256', $key) . '.json';

        $fp = @fopen($file, 'c+');
        if (!$fp) {
            // Fail open — a filesystem hiccup should never lock everyone out.
            return ['blocked' => false, 'retry_after' => 0];
        }

        flock($fp, LOCK_EX);
        $raw  = stream_get_contents($fp);
        $data = ($raw !== false && $raw !== '') ? json_decode($raw, true) : null;
        $now  = time();

        if (!is_array($data) || ($now - ($data['window_start'] ?? 0)) > $windowSeconds) {
            $data = ['window_start' => $now, 'count' => 0];
        }
        $data['count']++;

        ftruncate($fp, 0);
        rewind($fp);
        fwrite($fp, json_encode($data));
        fflush($fp);
        flock($fp, LOCK_UN);
        fclose($fp);

        $blocked    = $data['count'] > $maxAttempts;
        $retryAfter = $blocked ? max(0, $windowSeconds - ($now - $data['window_start'])) : 0;

        return ['blocked' => $blocked, 'retry_after' => $retryAfter];
    }
}
