<?php

declare(strict_types=1);

namespace App\Framework\RateLimit;

use App\Framework\RateLimit\Storage\StorageInterface;
use App\Framework\RateLimit\TimeProvider\SystemTimeProvider;
use App\Framework\RateLimit\TimeProvider\TimeProviderInterface;

final readonly class RateLimiter
{
    public function __construct(
        private StorageInterface $storage,
        private TimeProviderInterface $timeProvider = new SystemTimeProvider()
    ) {
    }

    public function checkLimit(string $key, int $limit, float $window): RateLimitResult
    {
        $now = $this->timeProvider->getCurrentTime();
        $windowStart = $now - (int) $window;

        // Sliding Window Log Implementierung
        $requests = $this->storage->getRequestsInWindow($key, $windowStart, $now);

        if (count($requests) >= $limit) {
            $oldestRequest = min($requests);
            $retryAfter = $oldestRequest + (int) $window - $now;

            return RateLimitResult::exceeded($limit, count($requests), max(1, $retryAfter));
        }

        // Request hinzufügen
        $this->storage->addRequest($key, $now, (int) $window);

        return RateLimitResult::allowed($limit, count($requests) + 1);
    }

    public function checkTokenBucket(string $key, int $capacity, int $refillRate, int $tokens = 1): RateLimitResult
    {
        $now = $this->timeProvider->getCurrentTime();
        $bucket = $this->storage->getTokenBucket($key);

        if ($bucket === null) {
            $bucket = new TokenBucket($capacity, $capacity, $now);
        }

        // Token nachfüllen
        $timePassed = $now - $bucket->lastRefill;
        $tokensToAdd = min($capacity - $bucket->tokens, intval($timePassed * $refillRate));

        $bucket = new TokenBucket(
            $bucket->capacity,
            $bucket->tokens + $tokensToAdd,
            $now
        );

        if ($bucket->tokens >= $tokens) {
            $bucket = new TokenBucket(
                $bucket->capacity,
                $bucket->tokens - $tokens,
                $bucket->lastRefill
            );

            $this->storage->saveTokenBucket($key, $bucket);

            return RateLimitResult::allowed($capacity, $bucket->tokens);
        }

        $this->storage->saveTokenBucket($key, $bucket);

        $retryAfter = intval(($tokens - $bucket->tokens) / $refillRate);

        return RateLimitResult::exceeded($capacity, $bucket->tokens, $retryAfter);
    }

    public function reset(string $key): void
    {
        $this->storage->clear($key);
    }

    public function getUsage(string $key, float $window): int
    {
        $now = $this->timeProvider->getCurrentTime();
        $windowStart = $now - (int) $window;

        return count($this->storage->getRequestsInWindow($key, $windowStart, $now));
    }

    // ===== WAF Intelligence Extensions =====

    /**
     * Advanced rate limiting with threat analysis
     */
    public function checkLimitWithAnalysis(
        string $key,
        int $limit,
        float $window,
        array $requestContext = []
    ): RateLimitResult {
        $basicResult = $this->checkLimit($key, $limit, $window);

        // If no context provided, return basic result
        if (empty($requestContext)) {
            return $basicResult;
        }

        // Perform traffic pattern analysis
        $analysisData = $this->analyzeTrafficPatterns($key, $window, $requestContext);

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

    /**
     * Check for burst patterns (potential attack indicator)
     */
    public function detectBurstPattern(string $key, float $window = 60.0): array
    {
        $now = $this->timeProvider->getCurrentTime();
        $requests = $this->storage->getRequestsInWindow($key, $now - (int) $window, $now);

        if (count($requests) < 5) {
            return ['burst_detected' => false, 'burst_intensity' => 0.0];
        }

        sort($requests);
        $intervals = [];

        for ($i = 1; $i < count($requests); $i++) {
            $intervals[] = $requests[$i] - $requests[$i - 1];
        }

        // Calculate burst intensity (lower intervals = higher intensity)
        $avgInterval = array_sum($intervals) / count($intervals);
        $minInterval = min($intervals);

        $burstIntensity = $avgInterval > 0 ? ($avgInterval - $minInterval) / $avgInterval : 1.0;
        $burstDetected = $burstIntensity > 0.8 && $minInterval < 2; // Very fast requests

        return [
            'burst_detected' => $burstDetected,
            'burst_intensity' => $burstIntensity,
            'min_interval' => $minInterval,
            'avg_interval' => $avgInterval,
            'request_count' => count($requests),
        ];
    }

    /**
     * Calculate baseline deviation for anomaly detection
     */
    public function calculateBaselineDeviation(string $baseKey, int $currentRate, int $analysisWindow = 3600): float
    {
        $historicalKey = $baseKey . ':baseline';
        $baseline = $this->storage->getBaseline($historicalKey) ?? [];

        if (empty($baseline)) {
            // Initialize baseline
            $this->storage->updateBaseline($historicalKey, $currentRate);

            return 0.0;
        }

        $mean = array_sum($baseline) / count($baseline);
        $variance = array_sum(array_map(fn ($x) => pow($x - $mean, 2), $baseline)) / count($baseline);
        $stdDev = sqrt($variance);

        // Update baseline with current rate
        $this->storage->updateBaseline($historicalKey, $currentRate);

        return $stdDev > 0 ? ($currentRate - $mean) / $stdDev : 0.0;
    }

    /**
     * Analyze traffic patterns for threat detection
     */
    private function analyzeTrafficPatterns(string $key, float $window, array $requestContext): array
    {
        $now = $this->timeProvider->getCurrentTime();
        $currentRate = $this->getUsage($key, $window);

        // Burst analysis
        $burstAnalysis = $this->detectBurstPattern($key, $window);

        // Baseline deviation
        $baselineDeviation = $this->calculateBaselineDeviation($key, $currentRate);

        // Calculate threat score
        $threatScore = $this->calculateThreatScore($burstAnalysis, $baselineDeviation, $requestContext);

        // Identify attack patterns
        $attackPatterns = $this->identifyAttackPatterns($requestContext, $burstAnalysis);

        // Anomaly indicators
        $anomalyIndicators = $this->identifyAnomalyIndicators($burstAnalysis, $baselineDeviation);

        return [
            'threat_score' => $threatScore,
            'baseline_deviation' => $baselineDeviation,
            'attack_patterns' => $attackPatterns,
            'anomaly_indicators' => $anomalyIndicators,
            'burst_analysis' => $burstAnalysis,
            'traffic_analysis' => [
                'current_rate' => $currentRate,
                'analysis_window' => $window,
                'timestamp' => $now,
            ],
            'confidence' => min(0.95, abs($baselineDeviation) * 0.3 + ($burstAnalysis['burst_intensity'] ?? 0) * 0.7),
            'client_ip' => $requestContext['client_ip'] ?? null,
            'request_path' => $requestContext['request_path'] ?? null,
            'user_agent' => $requestContext['user_agent'] ?? null,
        ];
    }

    /**
     * Calculate overall threat score
     */
    private function calculateThreatScore(array $burstAnalysis, float $baselineDeviation, array $requestContext): float
    {
        $score = 0.0;

        // Burst pattern scoring
        if ($burstAnalysis['burst_detected'] ?? false) {
            $score += ($burstAnalysis['burst_intensity'] ?? 0) * 40;
        }

        // Baseline deviation scoring
        if (abs($baselineDeviation) >= 3.0) {
            $score += min(40, abs($baselineDeviation) * 10);
        }

        // Suspicious user agent patterns
        $userAgent = strtolower($requestContext['user_agent'] ?? '');
        $suspiciousPatterns = ['bot', 'crawler', 'scanner', 'sqlmap', 'nikto', 'nmap'];
        foreach ($suspiciousPatterns as $pattern) {
            if (str_contains($userAgent, $pattern)) {
                $score += 20;

                break;
            }
        }

        return min(100.0, $score);
    }

    /**
     * Identify potential attack patterns
     */
    private function identifyAttackPatterns(array $requestContext, array $burstAnalysis): array
    {
        $patterns = [];

        if ($burstAnalysis['burst_detected'] ?? false) {
            $patterns[] = 'volumetric_attack';
        }

        if (($burstAnalysis['request_count'] ?? 0) > 100) {
            $patterns[] = 'flood_attack';
        }

        $userAgent = strtolower($requestContext['user_agent'] ?? '');
        if (str_contains($userAgent, 'sqlmap') || str_contains($userAgent, 'scanner')) {
            $patterns[] = 'automated_attack';
        }

        return $patterns;
    }

    /**
     * Identify anomaly indicators
     */
    private function identifyAnomalyIndicators(array $burstAnalysis, float $baselineDeviation): array
    {
        $indicators = [];

        if (abs($baselineDeviation) >= 3.0) {
            $indicators[] = 'traffic_anomaly';
        }

        if ($burstAnalysis['burst_detected'] ?? false) {
            $indicators[] = 'burst_pattern';
        }

        if (($burstAnalysis['min_interval'] ?? 10) < 0.5) {
            $indicators[] = 'suspicious_timing';
        }

        return $indicators;
    }
}
