<?php

declare(strict_types=1);

namespace BotGuard\Services;

use BotGuard\Core\Database;

/**
 * Durable per-IP request velocity tracker backing the RateLimit detection
 * module. Maintains fixed-window counters (per second/minute/hour) in the
 * rate_limits table using atomic upserts, and returns the current counts so the
 * engine can score request velocity.
 */
final class RateTracker
{
    public function __construct(private Database $db)
    {
    }

    /**
     * Register a hit for an IP and return the current window counts.
     *
     * @return array{per_second:int, per_minute:int, per_hour:int}
     */
    public function hit(string $ip): array
    {
        $now = time();
        $windows = [
            'per_second' => ['size' => 1,    'bucket' => $now],
            'per_minute' => ['size' => 60,   'bucket' => intdiv($now, 60)],
            'per_hour'   => ['size' => 3600, 'bucket' => intdiv($now, 3600)],
        ];

        $counts = [];

        foreach ($windows as $name => $w) {
            $key = "ip:{$ip}:{$name}:{$w['bucket']}";
            $windowStart = date('Y-m-d H:i:s', $w['bucket'] * $w['size']);
            $expires = date('Y-m-d H:i:s', $now + $w['size']);

            // Atomic increment-or-create.
            $this->db->run(
                'INSERT INTO `rate_limits` (`rl_key`, `hits`, `window_start`, `expires_at`)
                 VALUES (?, 1, ?, ?)
                 ON DUPLICATE KEY UPDATE `hits` = `hits` + 1',
                [$key, $windowStart, $expires]
            );

            $counts[$name] = (int) $this->db->scalar(
                'SELECT `hits` FROM `rate_limits` WHERE `rl_key` = ?',
                [$key]
            );
        }

        // Opportunistic cleanup of expired rows (~2% of calls).
        if (random_int(1, 50) === 1) {
            $this->db->affectingStatement('DELETE FROM `rate_limits` WHERE `expires_at` < ?', [now()]);
        }

        /** @var array{per_second:int, per_minute:int, per_hour:int} $counts */
        return $counts;
    }
}
