<?php
declare(strict_types=1);

namespace App\Security;

use InvalidArgumentException;
use PDO;

final class RateLimitService
{
    public function __construct(
        private readonly PDO $pdo,
    ) {
    }

    public function consume(string $identifier, string $action, int $maxAttempts, int $windowSeconds): bool
    {
        $identifier = trim($identifier);
        $action = trim($action);

        if ($identifier === '' || $action === '') {
            throw new InvalidArgumentException('Rate limit identifier and action are required.');
        }

        if ($maxAttempts <= 0 || $windowSeconds <= 0) {
            throw new InvalidArgumentException('Rate limit thresholds must be positive.');
        }

        $windowStart = $this->windowStart($windowSeconds);
        $identifierHash = hash('sha256', $identifier);

        $statement = $this->pdo->prepare(
            'INSERT INTO rate_limits (identifier_hash, action, window_start, count, created_at)
             VALUES (:identifier_hash, :action, :window_start, 1, NOW())
             ON DUPLICATE KEY UPDATE count = count + 1'
        );
        $statement->execute([
            'identifier_hash' => $identifierHash,
            'action' => $action,
            'window_start' => $windowStart,
        ]);

        $count = $this->pdo->prepare(
            'SELECT count
             FROM rate_limits
             WHERE identifier_hash = :identifier_hash
               AND action = :action
               AND window_start = :window_start
             LIMIT 1'
        );
        $count->execute([
            'identifier_hash' => $identifierHash,
            'action' => $action,
            'window_start' => $windowStart,
        ]);

        return (int) $count->fetchColumn() <= $maxAttempts;
    }

    public function clearExpired(int $olderThanSeconds = 86400): int
    {
        if ($olderThanSeconds < 0) {
            throw new InvalidArgumentException('Rate limit expiry must not be negative.');
        }

        $cutoff = gmdate('Y-m-d H:i:s', time() - $olderThanSeconds);
        $statement = $this->pdo->prepare('DELETE FROM rate_limits WHERE window_start < :cutoff');
        $statement->execute(['cutoff' => $cutoff]);

        return $statement->rowCount();
    }

    private function windowStart(int $windowSeconds): string
    {
        $timestamp = (int) floor(time() / $windowSeconds) * $windowSeconds;

        return gmdate('Y-m-d H:i:s', $timestamp);
    }
}
