<?php

declare(strict_types=1);

namespace App\Framework\DDoS\Analysis;

use App\Framework\Cache\Cache;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\Http\HttpRequest;
use App\Framework\Logging\Logger;
use App\Framework\RateLimit\RateLimiter;

/**
 * Traffic Pattern Analyzer - Uses existing RateLimiter for intelligent traffic analysis
 *
 * Analyzes traffic patterns to detect volumetric DDoS attacks by leveraging
 * the framework's existing RateLimit infrastructure.
 */
final class TrafficPatternAnalyzer
{
    private const string CACHE_PREFIX = 'traffic_pattern:';
    private const int BASELINE_SAMPLES = 100;
    private const float ANOMALY_THRESHOLD = 3.0; // Standard deviations

    public function __construct(
        private readonly Cache $cache,
        private readonly Clock $clock,
        private readonly Logger $logger
    ) {
    }

    /**
     * Analyze traffic patterns using existing RateLimiter infrastructure
     */
    public function analyzeTrafficPatterns(
        string $clientIp,
        HttpRequest $request,
        RateLimiter $rateLimiter,
        int $windowSeconds
    ): array {
        $analysisStart = $this->clock->time();

        // Generate analysis keys
        $ipKey = $this->generateKey('ip', $clientIp);
        $pathKey = $this->generateKey('path', $this->sanitizePath($request->path));
        $globalKey = $this->generateKey('global', 'all');

        // Analyze different traffic dimensions
        $analyses = [
            'ip_analysis' => $this->analyzeIpTraffic($clientIp, $rateLimiter, $windowSeconds),
            'path_analysis' => $this->analyzePathTraffic($request->path, $rateLimiter, $windowSeconds),
            'global_analysis' => $this->analyzeGlobalTraffic($rateLimiter, $windowSeconds),
            'burst_analysis' => $this->analyzeBurstPattern($clientIp, $rateLimiter),
            'temporal_analysis' => $this->analyzeTemporalPattern($clientIp),
        ];

        // Calculate combined threat score
        $threatScore = $this->calculateCombinedThreatScore($analyses);
        $confidence = $this->calculateAnalysisConfidence($analyses);

        // Update baseline statistics
        $this->updateBaseline($clientIp, $analyses['ip_analysis']['current_rate']);

        $processingTime = $this->clock->time()->diff($analysisStart);

        return [
            'threat_score' => $threatScore,
            'confidence' => $confidence,
            'processing_time' => $processingTime,
            'analysis_breakdown' => $analyses,
            'baseline_deviation' => $this->calculateBaselineDeviation($clientIp, $analyses['ip_analysis']['current_rate']),
            'anomaly_indicators' => $this->identifyAnomalyIndicators($analyses),
            'request_rate' => $analyses['ip_analysis']['current_rate'],
            'timestamp' => $this->clock->time(),
        ];
    }

    /**
     * Analyze traffic patterns for specific IP using RateLimiter
     */
    private function analyzeIpTraffic(string $clientIp, RateLimiter $rateLimiter, int $windowSeconds): array
    {
        $key = "ip_traffic:{$clientIp}";

        // Use RateLimiter to get current usage stats
        $currentUsage = $rateLimiter->getUsage($key, $windowSeconds);
        $currentRate = $currentUsage / ($windowSeconds / 60.0); // Convert to requests per minute

        // Get historical data from cache
        $historicalKey = $this->generateKey('history', $clientIp);
        $historicalRates = $this->cache->get($historicalKey, []);

        // Calculate statistics
        $avgRate = $this->calculateAverage($historicalRates);
        $stdDev = $this->calculateStandardDeviation($historicalRates);
        $deviation = $avgRate > 0 ? abs($currentRate - $avgRate) / max($avgRate, 1) : 0;

        // Detect anomalies
        $isAnomaly = $stdDev > 0 && abs($currentRate - $avgRate) > (self::ANOMALY_THRESHOLD * $stdDev);
        $anomalyScore = $this->calculateAnomalyScore($currentRate, $avgRate, $stdDev);

        // Store current rate for future analysis
        $historicalRates[] = $currentRate;
        if (count($historicalRates) > self::BASELINE_SAMPLES) {
            array_shift($historicalRates); // Keep only recent samples
        }
        $this->cache->set($historicalKey, $historicalRates, Duration::fromHours(24)->toCacheSeconds());

        return [
            'current_rate' => $currentRate,
            'average_rate' => $avgRate,
            'standard_deviation' => $stdDev,
            'deviation_ratio' => $deviation,
            'is_anomaly' => $isAnomaly,
            'anomaly_score' => $anomalyScore,
            'sample_count' => count($historicalRates),
            'raw_usage' => $currentUsage,
        ];
    }

    /**
     * Analyze traffic patterns for specific path
     */
    private function analyzePathTraffic(string $path, RateLimiter $rateLimiter, int $windowSeconds): array
    {
        $sanitizedPath = $this->sanitizePath($path);
        $key = "path_traffic:{$sanitizedPath}";

        $currentUsage = $rateLimiter->getUsage($key, $windowSeconds);
        $currentRate = $currentUsage / ($windowSeconds / 60.0);

        // Get path baseline
        $baselineKey = $this->generateKey('path_baseline', $sanitizedPath);
        $baseline = $this->cache->get($baselineKey, ['avg' => 0, 'max' => 0]);

        $isUnusual = $currentRate > ($baseline['avg'] * 5); // 5x normal traffic
        $severity = min(1.0, $currentRate / max($baseline['max'], 1));

        // Update baseline
        $newBaseline = [
            'avg' => ($baseline['avg'] * 0.9) + ($currentRate * 0.1), // Exponential moving average
            'max' => max($baseline['max'], $currentRate),
        ];
        $this->cache->set($baselineKey, $newBaseline, Duration::fromDays(7)->toCacheSeconds());

        return [
            'current_rate' => $currentRate,
            'baseline_avg' => $baseline['avg'],
            'baseline_max' => $baseline['max'],
            'is_unusual' => $isUnusual,
            'severity_score' => $severity,
            'path' => $sanitizedPath,
        ];
    }

    /**
     * Analyze global traffic patterns
     */
    private function analyzeGlobalTraffic(RateLimiter $rateLimiter, int $windowSeconds): array
    {
        $key = 'global_traffic:all';

        $currentUsage = $rateLimiter->getUsage($key, $windowSeconds);
        $currentRate = $currentUsage / ($windowSeconds / 60.0);

        // Get system capacity metrics
        $capacityKey = $this->generateKey('capacity', 'system');
        $capacity = $this->cache->get($capacityKey, ['normal' => 1000, 'max' => 5000]);

        $utilizationRate = $currentRate / $capacity['normal'];
        $isOverloaded = $currentRate > $capacity['max'];

        return [
            'current_rate' => $currentRate,
            'utilization_rate' => $utilizationRate,
            'is_overloaded' => $isOverloaded,
            'capacity_normal' => $capacity['normal'],
            'capacity_max' => $capacity['max'],
        ];
    }

    /**
     * Analyze burst patterns for rapid attack detection
     */
    private function analyzeBurstPattern(string $clientIp, RateLimiter $rateLimiter): array
    {
        $shortWindowKey = "burst:{$clientIp}:short";
        $mediumWindowKey = "burst:{$clientIp}:medium";

        // Check different time windows for burst detection
        $shortBurst = $rateLimiter->getUsage($shortWindowKey, 10); // 10 seconds
        $mediumBurst = $rateLimiter->getUsage($mediumWindowKey, 60); // 1 minute

        $shortRate = $shortBurst / (10 / 60.0); // Convert to per minute
        $mediumRate = $mediumBurst / 1.0;

        // Detect burst patterns
        $isBurst = $shortRate > ($mediumRate * 3); // 3x spike in short window
        $burstIntensity = $mediumRate > 0 ? $shortRate / $mediumRate : 0;

        return [
            'short_window_rate' => $shortRate,
            'medium_window_rate' => $mediumRate,
            'is_burst' => $isBurst,
            'burst_intensity' => min(10.0, $burstIntensity), // Cap at 10x
            'burst_score' => $this->calculateBurstScore($shortRate, $mediumRate),
        ];
    }

    /**
     * Analyze temporal patterns (timing regularity)
     */
    private function analyzeTemporalPattern(string $clientIp): array
    {
        $timestampKey = $this->generateKey('timestamps', $clientIp);
        $timestamps = $this->cache->get($timestampKey, []);

        // Add current timestamp
        $now = $this->clock->time();
        $timestamps[] = $now->toFloat();

        // Keep only recent timestamps (last 5 minutes)
        $fiveMinutesAgo = $now->toFloat() - 300;
        $timestamps = array_filter($timestamps, fn ($ts) => $ts > $fiveMinutesAgo);

        // Store updated timestamps
        $this->cache->set($timestampKey, $timestamps, Duration::fromMinutes(10)->toCacheSeconds());

        if (count($timestamps) < 3) {
            return [
                'sample_count' => count($timestamps),
                'is_regular' => false,
                'regularity_score' => 0.0,
                'average_interval' => 0.0,
            ];
        }

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

        $avgInterval = $this->calculateAverage($intervals);
        $intervalStdDev = $this->calculateStandardDeviation($intervals);

        // Regular timing suggests bot behavior
        $isRegular = $intervalStdDev < ($avgInterval * 0.1); // Less than 10% variance
        $regularityScore = $avgInterval > 0 ? min(1.0, (1.0 - ($intervalStdDev / $avgInterval))) : 0.0;

        return [
            'sample_count' => count($timestamps),
            'is_regular' => $isRegular,
            'regularity_score' => $regularityScore,
            'average_interval' => $avgInterval,
            'interval_std_dev' => $intervalStdDev,
            'intervals' => array_slice($intervals, -5), // Last 5 intervals for debugging
        ];
    }

    private function calculateCombinedThreatScore(array $analyses): float
    {
        $weights = [
            'ip_analysis' => 0.4,      // Highest weight - primary indicator
            'burst_analysis' => 0.25,   // Burst patterns are critical
            'path_analysis' => 0.15,    // Path-specific anomalies
            'global_analysis' => 0.1,   // System-wide impact
            'temporal_analysis' => 0.1,  // Bot behavior detection
        ];

        $totalScore = 0.0;

        // IP analysis contribution
        if (isset($analyses['ip_analysis']['anomaly_score'])) {
            $totalScore += $analyses['ip_analysis']['anomaly_score'] * $weights['ip_analysis'];
        }

        // Burst analysis contribution
        if (isset($analyses['burst_analysis']['burst_score'])) {
            $totalScore += $analyses['burst_analysis']['burst_score'] * $weights['burst_analysis'];
        }

        // Path analysis contribution
        if (isset($analyses['path_analysis']['severity_score'])) {
            $totalScore += $analyses['path_analysis']['severity_score'] * $weights['path_analysis'];
        }

        // Global analysis contribution
        if (isset($analyses['global_analysis']['utilization_rate'])) {
            $globalScore = min(1.0, $analyses['global_analysis']['utilization_rate']);
            $totalScore += $globalScore * $weights['global_analysis'];
        }

        // Temporal analysis contribution (regular patterns suggest bots)
        if (isset($analyses['temporal_analysis']['regularity_score'])) {
            $totalScore += $analyses['temporal_analysis']['regularity_score'] * $weights['temporal_analysis'];
        }

        return min(1.0, $totalScore);
    }

    private function calculateAnalysisConfidence(array $analyses): float
    {
        $confidenceFactors = [];

        // More samples = higher confidence
        if (isset($analyses['ip_analysis']['sample_count'])) {
            $sampleConfidence = min(1.0, $analyses['ip_analysis']['sample_count'] / self::BASELINE_SAMPLES);
            $confidenceFactors[] = $sampleConfidence;
        }

        // Multiple analysis layers agreeing = higher confidence
        $agreementScore = 0.0;
        $layerCount = 0;

        foreach ($analyses as $analysis) {
            if (is_array($analysis) && isset($analysis['is_anomaly']) && $analysis['is_anomaly']) {
                $agreementScore += 1.0;
            }
            $layerCount++;
        }

        if ($layerCount > 0) {
            $confidenceFactors[] = $agreementScore / $layerCount;
        }

        // Statistical significance
        if (isset($analyses['ip_analysis']['standard_deviation'])) {
            $statSig = $analyses['ip_analysis']['standard_deviation'] > 0 ? 1.0 : 0.5;
            $confidenceFactors[] = $statSig;
        }

        return count($confidenceFactors) > 0 ?
            array_sum($confidenceFactors) / count($confidenceFactors) : 0.0;
    }

    // Helper methods
    private function generateKey(string $type, string $identifier): string
    {
        return self::CACHE_PREFIX . $type . ':' . hash('sha256', $identifier);
    }

    private function sanitizePath(string $path): string
    {
        // Remove query parameters and normalize path
        $path = parse_url($path, PHP_URL_PATH) ?? $path;

        return trim($path, '/') ?: 'root';
    }

    private function calculateAverage(array $values): float
    {
        return count($values) > 0 ? array_sum($values) / count($values) : 0.0;
    }

    private function calculateStandardDeviation(array $values): float
    {
        if (count($values) < 2) {
            return 0.0;
        }

        $mean = $this->calculateAverage($values);
        $sumSquares = array_sum(array_map(fn ($x) => pow($x - $mean, 2), $values));

        return sqrt($sumSquares / (count($values) - 1));
    }

    private function calculateAnomalyScore(float $current, float $average, float $stdDev): float
    {
        if ($stdDev <= 0) {
            return 0.0;
        }

        $zScore = abs($current - $average) / $stdDev;

        // Convert z-score to 0-1 scale (3+ standard deviations = 1.0)
        return min(1.0, $zScore / self::ANOMALY_THRESHOLD);
    }

    private function calculateBurstScore(float $shortRate, float $mediumRate): float
    {
        if ($mediumRate <= 0) {
            return $shortRate > 100 ? 1.0 : 0.0;
        } // Arbitrary threshold for new IPs

        $ratio = $shortRate / $mediumRate;

        // Score based on intensity (3x = 0.5, 6x = 1.0)
        return min(1.0, max(0.0, ($ratio - 1.0) / 5.0));
    }

    private function updateBaseline(string $clientIp, float $currentRate): void
    {
        $baselineKey = $this->generateKey('baseline', $clientIp);
        $baseline = $this->cache->get($baselineKey, ['count' => 0, 'sum' => 0.0, 'avg' => 0.0]);

        $baseline['count']++;
        $baseline['sum'] += $currentRate;
        $baseline['avg'] = $baseline['sum'] / $baseline['count'];

        // Keep baseline fresh (exponential decay)
        if ($baseline['count'] > self::BASELINE_SAMPLES) {
            $baseline['count'] = self::BASELINE_SAMPLES;
            $baseline['sum'] = $baseline['avg'] * self::BASELINE_SAMPLES;
        }

        $this->cache->set($baselineKey, $baseline, Duration::fromDays(7)->toCacheSeconds());
    }

    private function calculateBaselineDeviation(string $clientIp, float $currentRate): float
    {
        $baselineKey = $this->generateKey('baseline', $clientIp);
        $baseline = $this->cache->get($baselineKey, ['avg' => 0.0]);

        if ($baseline['avg'] <= 0) {
            return 0.0;
        }

        return abs($currentRate - $baseline['avg']) / $baseline['avg'];
    }

    private function identifyAnomalyIndicators(array $analyses): array
    {
        $indicators = [];

        if ($analyses['ip_analysis']['is_anomaly'] ?? false) {
            $indicators[] = 'ip_traffic_anomaly';
        }

        if ($analyses['burst_analysis']['is_burst'] ?? false) {
            $indicators[] = 'burst_pattern';
        }

        if ($analyses['path_analysis']['is_unusual'] ?? false) {
            $indicators[] = 'unusual_path_access';
        }

        if ($analyses['global_analysis']['is_overloaded'] ?? false) {
            $indicators[] = 'system_overload';
        }

        if ($analyses['temporal_analysis']['is_regular'] ?? false) {
            $indicators[] = 'regular_timing_pattern';
        }

        return $indicators;
    }
}
