<?php

namespace PortalPeople\virtualstock\components;

use PortalPeople\virtualstock\exceptions\RateLimitException;
use yii\base\Component;
use Yii;

/**
 * Sliding-window rate limiter for VirtualStock API calls.
 *
 * VirtualStock enforces a 60 requests/minute limit. This component tracks
 * request timestamps in Yii cache and raises RateLimitException before
 * sending a request that would exceed the limit.
 *
 * Usage:
 *   $limiter->check(); // throws RateLimitException if at capacity
 *   // ... send request ...
 *   $limiter->record(); // record successful send
 */
class RateLimiter extends Component
{
    /** Maximum number of requests allowed per window. */
    public int $maxRequests = 60;

    /** Window duration in seconds. */
    public int $windowSeconds = 60;

    /** Cache key prefix. */
    public string $cacheKey = 'vs_rate_limit';

    /**
     * Check whether a request can be sent.
     * Does NOT record the request — call record() after the request is dispatched.
     *
     * @throws RateLimitException if the rate limit has been reached
     */
    public function check(): void
    {
        $timestamps = $this->getTimestamps();

        if (count($timestamps) >= $this->maxRequests) {
            $oldest  = $timestamps[0];
            $resetAt = $oldest + $this->windowSeconds;
            $wait    = max(0, $resetAt - time());

            throw new RateLimitException(
                sprintf(
                    'VirtualStock API rate limit reached (%d/%d req/min). Retry in %ds.',
                    $this->maxRequests,
                    $this->maxRequests,
                    $wait
                ),
                $wait > 0 ? (string) $wait : null
            );
        }
    }

    /**
     * Record that a request was sent right now.
     */
    public function record(): void
    {
        $timestamps   = $this->getTimestamps();
        $timestamps[] = time();
        $this->saveTimestamps($timestamps);
    }

    /**
     * Returns the number of requests consumed in the current window.
     */
    public function getUsed(): int
    {
        return count($this->getTimestamps());
    }

    /**
     * Returns remaining requests available in the current window.
     */
    public function getRemaining(): int
    {
        return max(0, $this->maxRequests - $this->getUsed());
    }

    /**
     * Reset the limiter (useful in tests or after a 429 back-off).
     */
    public function reset(): void
    {
        Yii::$app->cache->delete($this->cacheKey);
    }

    /**
     * Return timestamps from the current sliding window (pruning expired entries).
     *
     * @return int[]
     */
    private function getTimestamps(): array
    {
        $raw = Yii::$app->cache->get($this->cacheKey);
        /** @var int[] $all */
        $all    = is_array($raw) ? $raw : [];
        $cutoff = time() - $this->windowSeconds;

        return array_values(array_filter($all, static fn(int $ts): bool => $ts > $cutoff));
    }

    /**
     * @param int[] $timestamps
     */
    private function saveTimestamps(array $timestamps): void
    {
        Yii::$app->cache->set($this->cacheKey, $timestamps, $this->windowSeconds + 5);
    }
}
