<?php

declare(strict_types=1);

namespace App\Framework\Waf\BotProtection;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\DateTime\Clock;
use App\Framework\Logging\Logger;
use App\Framework\Waf\Analysis\ValueObjects\RequestAnalysisData;
use App\Framework\Waf\BotProtection\Detectors\BehavioralDetector;
use App\Framework\Waf\BotProtection\Detectors\DeviceIntelligenceDetector;
use App\Framework\Waf\BotProtection\Detectors\FingerprintDetector;
use App\Framework\Waf\BotProtection\ValueObjects\BotDetectionResult;
use App\Framework\Waf\BotProtection\ValueObjects\BotRiskScore;
use App\Framework\Waf\BotProtection\ValueObjects\BrowserFingerprint;
use App\Framework\Waf\BotProtection\ValueObjects\DeviceProfile;

/**
 * Advanced Bot Protection Engine
 * Combines multiple detection techniques for comprehensive bot detection
 */
final class BotProtectionEngine
{
    public function __construct(
        private readonly FingerprintDetector $fingerprintDetector,
        private readonly BehavioralDetector $behavioralDetector,
        private readonly DeviceIntelligenceDetector $deviceDetector,
        private readonly CaptchaValidator $captchaValidator,
        private readonly Clock $clock,
        private readonly Logger $logger,
        private readonly bool $enabled = true,
        private readonly ?Percentage $botThreshold = null,
        private readonly ?Duration $analysisTimeout = null,
        private readonly bool $enableFingerprinting = true,
        private readonly bool $enableBehavioralAnalysis = true,
        private readonly bool $enableDeviceIntelligence = true,
        private readonly bool $enableCaptcha = true,
        private readonly int $maxFingerprintAge = 86400, // 24 hours
        private array $detectionHistory = [],
        private array $performanceMetrics = []
    ) {
    }

    /**
     * Get bot threshold with default fallback
     */
    private function getBotThreshold(): Percentage
    {
        return $this->botThreshold ?? Percentage::from(75.0);
    }

    /**
     * Get analysis timeout with default fallback
     */
    private function getAnalysisTimeout(): Duration
    {
        return $this->analysisTimeout ?? Duration::fromSeconds(2);
    }

    /**
     * Analyze request for bot indicators
     */
    public function analyzeRequest(RequestAnalysisData $requestData, array $context = []): BotDetectionResult
    {
        $startTime = $this->clock->time();

        if (! $this->enabled) {
            return BotDetectionResult::disabled();
        }

        try {
            $detections = [];
            $fingerprint = null;
            $deviceProfile = null;
            $riskFactors = [];

            // Browser fingerprinting detection
            if ($this->enableFingerprinting) {
                $fingerprint = $this->detectFingerprint($requestData, $context);
                if ($fingerprint->isAnomalous()) {
                    $detections[] = $this->createFingerprintDetection($fingerprint);
                    $riskFactors['fingerprint_anomaly'] = $fingerprint->getAnomalyScore();
                }
            }

            // Behavioral analysis
            if ($this->enableBehavioralAnalysis) {
                $behavioralResult = $this->analyzeBehavior($requestData, $context);
                if ($behavioralResult->isSuspicious()) {
                    $detections = array_merge($detections, $behavioralResult->getDetections());
                    $riskFactors['behavioral_anomaly'] = $behavioralResult->getRiskScore();
                }
            }

            // Device intelligence
            if ($this->enableDeviceIntelligence) {
                $deviceProfile = $this->analyzeDevice($requestData, $context);
                if ($deviceProfile->isSuspicious()) {
                    $detections[] = $this->createDeviceDetection($deviceProfile);
                    $riskFactors['device_anomaly'] = $deviceProfile->getSuspicionScore();
                }
            }

            // Calculate overall bot risk score
            $botRiskScore = $this->calculateBotRiskScore($riskFactors, $detections);

            // CAPTCHA challenge if risk is elevated
            $captchaRequired = $this->shouldRequireCaptcha($botRiskScore, $context);
            $captchaValid = false;

            if ($captchaRequired && $this->enableCaptcha) {
                $captchaValid = $this->validateCaptcha($requestData, $context);
                if (! $captchaValid) {
                    $detections[] = $this->createCaptchaFailureDetection();
                }
            }

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

            // Record detection history
            $this->recordDetection($requestData, $botRiskScore, $detections);

            // Update performance metrics
            $this->updatePerformanceMetrics($processingTime, count($detections));

            return new BotDetectionResult(
                isBot: $botRiskScore->getValue() >= $this->getBotThreshold()->getValue(),
                riskScore: $botRiskScore,
                detections: $detections,
                fingerprint: $fingerprint,
                deviceProfile: $deviceProfile,
                captchaRequired: $captchaRequired,
                captchaValid: $captchaValid,
                processingTime: $processingTime,
                riskFactors: $riskFactors,
                confidence: $this->calculateConfidence($detections, $riskFactors)
            );

        } catch (\Throwable $e) {
            $processingTime = $startTime->diff($this->clock->time());

            $this->logger->error('Bot protection analysis failed', [
                'error' => $e->getMessage(),
                'request_id' => $context['request_id'] ?? null,
                'processing_time' => $processingTime->toMilliseconds(),
            ]);

            return BotDetectionResult::error($e->getMessage(), $processingTime);
        }
    }

    /**
     * Detect browser fingerprint anomalies
     */
    private function detectFingerprint(RequestAnalysisData $requestData, array $context): BrowserFingerprint
    {
        return $this->fingerprintDetector->analyzeFingerprint($requestData, $context);
    }

    /**
     * Analyze behavioral patterns
     */
    private function analyzeBehavior(RequestAnalysisData $requestData, array $context): BehavioralAnalysisResult
    {
        return $this->behavioralDetector->analyzeBehavior($requestData, $context);
    }

    /**
     * Analyze device characteristics
     */
    private function analyzeDevice(RequestAnalysisData $requestData, array $context): DeviceProfile
    {
        return $this->deviceDetector->analyzeDevice($requestData, $context);
    }

    /**
     * Calculate overall bot risk score
     */
    private function calculateBotRiskScore(array $riskFactors, array $detections): BotRiskScore
    {
        $totalRisk = 0.0;
        $maxWeight = 0.0;

        // Weight risk factors
        $weights = [
            'fingerprint_anomaly' => 0.3,
            'behavioral_anomaly' => 0.4,
            'device_anomaly' => 0.2,
            'captcha_failure' => 0.6,
        ];

        foreach ($riskFactors as $factor => $score) {
            $weight = $weights[$factor] ?? 0.1;
            $totalRisk += $score * $weight;
            $maxWeight += $weight;
        }

        // Apply detection count multiplier
        $detectionMultiplier = 1.0 + (count($detections) * 0.1);
        $finalScore = ($maxWeight > 0) ? ($totalRisk / $maxWeight) * $detectionMultiplier : 0.0;

        return new BotRiskScore(
            score: Percentage::from(min(100.0, $finalScore * 100)),
            factors: $riskFactors,
            detectionCount: count($detections),
            confidence: $this->calculateRiskConfidence($riskFactors)
        );
    }

    /**
     * Determine if CAPTCHA challenge is required
     */
    private function shouldRequireCaptcha(BotRiskScore $riskScore, array $context): bool
    {
        // Require CAPTCHA for medium-high risk scores
        if ($riskScore->getValue() >= 50.0) {
            return true;
        }

        // Require CAPTCHA for repeated suspicious activity
        $clientIdentifier = $this->getClientIdentifier($context);
        $recentDetections = $this->getRecentDetections($clientIdentifier);

        if (count($recentDetections) >= 3) {
            return true;
        }

        return false;
    }

    /**
     * Validate CAPTCHA response
     */
    private function validateCaptcha(RequestAnalysisData $requestData, array $context): bool
    {
        return $this->captchaValidator->validateResponse($requestData, $context);
    }

    /**
     * Create fingerprint-based detection
     */
    private function createFingerprintDetection(BrowserFingerprint $fingerprint): BotDetection
    {
        return new BotDetection(
            type: BotDetectionType::FINGERPRINT_ANOMALY,
            severity: $fingerprint->getAnomalyScore() > 80 ? DetectionSeverity::HIGH : DetectionSeverity::MEDIUM,
            message: 'Suspicious browser fingerprint detected',
            evidence: $fingerprint->getAnomalousFeatures(),
            confidence: Percentage::from($fingerprint->getAnomalyScore()),
            source: 'fingerprint_detector'
        );
    }

    /**
     * Create device-based detection
     */
    private function createDeviceDetection(DeviceProfile $deviceProfile): BotDetection
    {
        return new BotDetection(
            type: BotDetectionType::DEVICE_ANOMALY,
            severity: $deviceProfile->getSuspicionScore() > 80 ? DetectionSeverity::HIGH : DetectionSeverity::MEDIUM,
            message: 'Suspicious device characteristics detected',
            evidence: $deviceProfile->getSuspiciousFeatures(),
            confidence: Percentage::from($deviceProfile->getSuspicionScore()),
            source: 'device_detector'
        );
    }

    /**
     * Create CAPTCHA failure detection
     */
    private function createCaptchaFailureDetection(): BotDetection
    {
        return new BotDetection(
            type: BotDetectionType::CAPTCHA_FAILURE,
            severity: DetectionSeverity::HIGH,
            message: 'CAPTCHA validation failed',
            evidence: ['captcha_validation' => 'failed'],
            confidence: Percentage::from(95.0),
            source: 'captcha_validator'
        );
    }

    /**
     * Calculate detection confidence
     */
    private function calculateConfidence(array $detections, array $riskFactors): Percentage
    {
        if (empty($detections) && empty($riskFactors)) {
            return Percentage::from(0.0);
        }

        $totalConfidence = 0.0;
        $count = 0;

        // Average detection confidences
        foreach ($detections as $detection) {
            if ($detection instanceof BotDetection) {
                $totalConfidence += $detection->confidence->getValue();
                $count++;
            }
        }

        // Factor in risk score consistency
        $riskScoreVariance = $this->calculateRiskScoreVariance($riskFactors);
        $consistencyBonus = max(0, 20 - $riskScoreVariance); // Higher consistency = higher confidence

        $averageConfidence = $count > 0 ? $totalConfidence / $count : 50.0;
        $finalConfidence = min(100.0, $averageConfidence + $consistencyBonus);

        return Percentage::from($finalConfidence);
    }

    /**
     * Calculate risk confidence
     */
    private function calculateRiskConfidence(array $riskFactors): Percentage
    {
        $factorCount = count($riskFactors);

        if ($factorCount === 0) {
            return Percentage::from(0.0);
        }

        // More consistent risk factors = higher confidence
        $variance = $this->calculateRiskScoreVariance($riskFactors);
        $baseConfidence = min(100.0, $factorCount * 25); // Up to 100% with 4+ factors
        $variancePenalty = min(30.0, $variance); // Up to 30% penalty for high variance

        return Percentage::from(max(0.0, $baseConfidence - $variancePenalty));
    }

    /**
     * Calculate variance in risk factor scores
     */
    private function calculateRiskScoreVariance(array $riskFactors): float
    {
        if (count($riskFactors) < 2) {
            return 0.0;
        }

        $mean = array_sum($riskFactors) / count($riskFactors);
        $squaredDiffs = array_map(fn ($score) => pow($score - $mean, 2), $riskFactors);

        return sqrt(array_sum($squaredDiffs) / count($squaredDiffs));
    }

    /**
     * Get client identifier for tracking
     */
    private function getClientIdentifier(array $context): string
    {
        return md5(implode('|', [
            $context['client_ip'] ?? '',
            $context['user_agent'] ?? '',
            $context['accept_language'] ?? '',
        ]));
    }

    /**
     * Get recent detections for client
     */
    private function getRecentDetections(string $clientIdentifier): array
    {
        $cutoff = $this->clock->time()->toUnixTimestamp() - 3600; // Last hour

        return array_filter(
            $this->detectionHistory[$clientIdentifier] ?? [],
            fn ($detection) => $detection['timestamp'] > $cutoff
        );
    }

    /**
     * Record detection in history
     */
    private function recordDetection(RequestAnalysisData $requestData, BotRiskScore $riskScore, array $detections): void
    {
        $clientIdentifier = $this->getClientIdentifier([
            'client_ip' => $requestData->clientIp?->toString(),
            'user_agent' => $requestData->userAgent?->toString(),
            'accept_language' => $requestData->headers['accept-language'] ?? '',
        ]);

        if (! isset($this->detectionHistory[$clientIdentifier])) {
            $this->detectionHistory[$clientIdentifier] = [];
        }

        $this->detectionHistory[$clientIdentifier][] = [
            'timestamp' => $this->clock->time()->toUnixTimestamp(),
            'risk_score' => $riskScore->getValue(),
            'detection_count' => count($detections),
            'is_bot' => $riskScore->getValue() >= $this->getBotThreshold()->getValue(),
        ];

        // Limit history size per client
        if (count($this->detectionHistory[$clientIdentifier]) > 100) {
            array_shift($this->detectionHistory[$clientIdentifier]);
        }

        // Limit total clients tracked
        if (count($this->detectionHistory) > 10000) {
            $oldestClient = array_key_first($this->detectionHistory);
            unset($this->detectionHistory[$oldestClient]);
        }
    }

    /**
     * Update performance metrics
     */
    private function updatePerformanceMetrics(Duration $processingTime, int $detectionCount): void
    {
        $this->performanceMetrics[] = [
            'timestamp' => $this->clock->time()->toUnixTimestamp(),
            'processing_time_ms' => $processingTime->toMilliseconds(),
            'detection_count' => $detectionCount,
        ];

        // Limit metrics history
        if (count($this->performanceMetrics) > 1000) {
            array_shift($this->performanceMetrics);
        }
    }

    /**
     * Get performance statistics
     */
    public function getPerformanceStats(): array
    {
        if (empty($this->performanceMetrics)) {
            return [];
        }

        $processingTimes = array_column($this->performanceMetrics, 'processing_time_ms');
        $detectionCounts = array_column($this->performanceMetrics, 'detection_count');

        return [
            'total_requests' => count($this->performanceMetrics),
            'avg_processing_time_ms' => array_sum($processingTimes) / count($processingTimes),
            'max_processing_time_ms' => max($processingTimes),
            'avg_detection_count' => array_sum($detectionCounts) / count($detectionCounts),
            'bot_detection_rate' => $this->calculateBotDetectionRate(),
        ];
    }

    /**
     * Calculate bot detection rate
     */
    private function calculateBotDetectionRate(): float
    {
        $totalDetections = 0;
        $botDetections = 0;

        foreach ($this->detectionHistory as $clientHistory) {
            foreach ($clientHistory as $detection) {
                $totalDetections++;
                if ($detection['is_bot']) {
                    $botDetections++;
                }
            }
        }

        return $totalDetections > 0 ? ($botDetections / $totalDetections) * 100 : 0.0;
    }

    /**
     * Check if engine is enabled
     */
    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    /**
     * Get configuration
     */
    public function getConfiguration(): array
    {
        return [
            'enabled' => $this->enabled,
            'bot_threshold' => $this->getBotThreshold()->getValue(),
            'analysis_timeout_ms' => $this->getAnalysisTimeout()->toMilliseconds(),
            'fingerprinting_enabled' => $this->enableFingerprinting,
            'behavioral_analysis_enabled' => $this->enableBehavioralAnalysis,
            'device_intelligence_enabled' => $this->enableDeviceIntelligence,
            'captcha_enabled' => $this->enableCaptcha,
            'max_fingerprint_age_seconds' => $this->maxFingerprintAge,
            'tracked_clients' => count($this->detectionHistory),
        ];
    }
}
