<?php

declare(strict_types=1);

namespace PpPhp\Core\Guard;

/**
 * Lightweight file-backed sliding-window rate limiter (feature C3).
 *
 * Guards write endpoints (A6 actions, form posts) against abuse: at most $max
 * hits per $window seconds for a key (typically "action|IP"). Best-effort by
 * design — a rare race under heavy concurrency may over-count slightly, which is
 * fine for spam control and avoids a hard dependency on Redis/DB.
 *
 * Pure enough to unit-test: pass a temp dir + an injected clock.
 */
final class RateLimiter
{
    /**
     * Record a hit and report whether it is within the limit.
     *
     * @return bool true = allowed (hit recorded), false = limit exceeded
     */
    public static function allow(string $key, int $max, int $window, string $dir, ?int $now = null): bool
    {
        $now ??= time();
        $max = max(1, $max);
        $window = max(1, $window);
        if (!is_dir($dir)) {
            @mkdir($dir, 0700, true);
        }
        $file = $dir . '/rl_' . hash('sha256', $key) . '.json';

        $hits = [];
        if (is_file($file)) {
            $data = json_decode((string) @file_get_contents($file), true);
            if (is_array($data) && is_array($data['hits'] ?? null)) {
                foreach ($data['hits'] as $t) {
                    if (($now - (int) $t) < $window) {
                        $hits[] = (int) $t;                // keep only hits still inside the window
                    }
                }
            }
        }

        if (count($hits) >= $max) {
            @file_put_contents($file, (string) json_encode(['hits' => $hits]));   // persist the pruned list
            return false;
        }

        $hits[] = $now;
        @file_put_contents($file, (string) json_encode(['hits' => $hits]));
        return true;
    }

    /** Convenience key: "<action>|<client-ip>". */
    public static function keyFor(string $action): string
    {
        return $action . '|' . (string) ($_SERVER['REMOTE_ADDR'] ?? 'cli');
    }
}
