<?php

declare(strict_types=1);

namespace App\Framework\RateLimit;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\RateLimit\TimeProvider\SystemTimeProvider;
use App\Framework\RateLimit\TimeProvider\TimeProviderInterface;
use App\Framework\SlidingWindow\SlidingWindow;
use App\Framework\SlidingWindow\SlidingWindowFactory;

/**
 * Token bucket implementation using SlidingWindow for analytics and tracking
 */
final readonly class SlidingWindowTokenBucket
{
    private SlidingWindow $tokenWindow;

    private SlidingWindow $analyticsWindow;

    public function __construct(
        private string $identifier,
        private int $capacity,
        private int $refillRate,
        SlidingWindowFactory $windowFactory,
        private TimeProviderInterface $timeProvider = new SystemTimeProvider(),
        private Duration $refillInterval = Duration::SECOND,
        private bool $persistent = true
    ) {
        // Window for tracking token consumption (short window for actual limiting)
        $this->tokenWindow = $windowFactory->createNumericWindow(
            identifier: "token_bucket:{$this->identifier}",
            windowSize: Duration::fromMinutes(1), // Short window for token tracking
            persistent: $this->persistent
        );

        // Window for analytics and threat detection (longer window)
        $this->analyticsWindow = $windowFactory->createRateLimitWindow(
            identifier: "token_analytics:{$this->identifier}",
            windowSize: Duration::fromMinutes(5), // Longer window for pattern analysis
            persistent: $this->persistent
        );
    }

    /**
     * Attempt to consume tokens
     */
    public function consume(int $tokens = 1): RateLimitResult
    {
        $now = Timestamp::fromFloat($this->timeProvider->getCurrentTime());

        // Record consumption attempt for analytics
        $this->analyticsWindow->record($tokens, $now);

        // Get current bucket state
        $currentTokens = $this->getCurrentTokens($now);

        if ($currentTokens >= $tokens) {
            // Consume tokens by recording the consumption
            $this->tokenWindow->record($tokens, $now);

            $remainingTokens = $currentTokens - $tokens;

            return RateLimitResult::allowed($this->capacity, $remainingTokens);
        }

        // Calculate retry after based on refill rate
        $tokensNeeded = $tokens - $currentTokens;
        $retryAfter = intval(ceil($tokensNeeded / $this->refillRate) * $this->refillInterval->toSeconds());

        return RateLimitResult::exceeded($this->capacity, $currentTokens, $retryAfter);
    }

    /**
     * Consume tokens with advanced analytics
     */
    public function consumeWithAnalysis(int $tokens = 1, array $requestContext = []): RateLimitResult
    {
        $basicResult = $this->consume($tokens);

        // Get analytics from the analytics window
        $now = Timestamp::fromFloat($this->timeProvider->getCurrentTime());
        $stats = $this->analyticsWindow->getStats($now);

        $analysisData = [
            'token_consumption' => [
                'requested' => $tokens,
                'available' => $basicResult->getCurrent(),
                'capacity' => $this->capacity,
                'refill_rate' => $this->refillRate,
            ],
            'sliding_window_stats' => [
                'window_start' => $stats->windowStart->toFloat(),
                'window_end' => $stats->windowEnd->toFloat(),
                'total_requests' => $stats->totalCount,
            ],
        ];

        // Add advanced analytics if available
        if (isset($stats->aggregatedData['rate_limit'])) {
            $rateLimitResult = $stats->aggregatedData['rate_limit'];
            if (method_exists($rateLimitResult, 'getBurstAnalysis')) {
                $analysisData['burst_analysis'] = $rateLimitResult->getBurstAnalysis();
                $analysisData['threat_analysis'] = $rateLimitResult->threatAnalysis;
            }
        }

        // Enhance with request context
        if (! empty($requestContext)) {
            $analysisData = array_merge($analysisData, [
                'client_ip' => $requestContext['client_ip'] ?? null,
                'user_agent' => $requestContext['user_agent'] ?? null,
                'request_path' => $requestContext['request_path'] ?? null,
            ]);
        }

        return RateLimitResult::withThreatAnalysis(
            $basicResult->isAllowed(),
            $basicResult->getLimit(),
            $basicResult->getCurrent(),
            $analysisData,
            $basicResult->getRetryAfter()
        );
    }

    /**
     * Check current token availability without consuming
     */
    public function peek(): int
    {
        $now = Timestamp::fromFloat($this->timeProvider->getCurrentTime());

        return $this->getCurrentTokens($now);
    }

    /**
     * Reset the token bucket
     */
    public function reset(): void
    {
        $this->tokenWindow->clear();
        $this->analyticsWindow->clear();
    }

    /**
     * Get bucket capacity
     */
    public function getCapacity(): int
    {
        return $this->capacity;
    }

    /**
     * Get refill rate (tokens per second)
     */
    public function getRefillRate(): int
    {
        return $this->refillRate;
    }

    /**
     * Get analytics data
     */
    public function getAnalytics(): array
    {
        $now = Timestamp::fromFloat($this->timeProvider->getCurrentTime());
        $stats = $this->analyticsWindow->getStats($now);

        return [
            'identifier' => $this->identifier,
            'capacity' => $this->capacity,
            'refill_rate' => $this->refillRate,
            'current_tokens' => $this->getCurrentTokens($now),
            'analytics_window' => [
                'total_requests' => $stats->totalCount,
                'window_start' => $stats->windowStart->toFloat(),
                'window_end' => $stats->windowEnd->toFloat(),
                'aggregated_data' => $stats->aggregatedData,
            ],
        ];
    }

    /**
     * Calculate current available tokens based on refill rate and consumption
     */
    private function getCurrentTokens(Timestamp $now): int
    {
        // Get consumption in the last refill period
        $refillWindowSize = Duration::fromSeconds(
            intval($this->capacity / $this->refillRate) + 60 // Buffer for precision
        );

        $tokenStats = $this->tokenWindow->getStats($now);

        // Calculate tokens consumed in the refill window
        $tokensConsumed = 0;
        if (isset($tokenStats->aggregatedData['numeric'])) {
            $numericResult = $tokenStats->aggregatedData['numeric'];
            if (method_exists($numericResult, 'getSum')) {
                $tokensConsumed = intval($numericResult->getSum());
            }
        }

        // Calculate tokens refilled since the oldest consumption
        $windowStart = $tokenStats->windowStart->toFloat();
        $timeSinceWindowStart = $now->toFloat() - $windowStart;
        $tokensRefilled = min(
            $this->capacity,
            intval($timeSinceWindowStart * $this->refillRate)
        );

        // Available tokens = capacity - consumed + refilled, capped at capacity
        $availableTokens = min(
            $this->capacity,
            max(0, $this->capacity - $tokensConsumed + $tokensRefilled)
        );

        return $availableTokens;
    }
}
