<?php

declare(strict_types=1);

namespace App\Framework\Waf\BotProtection\Detectors;

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\BotDetectionType;
use App\Framework\Waf\BotProtection\ValueObjects\BehavioralAnalysisResult;
use App\Framework\Waf\BotProtection\ValueObjects\BotDetection;
use App\Framework\Waf\DetectionSeverity;

/**
 * Behavioral analysis detector for automated patterns
 */
final class BehavioralDetector
{
    public function __construct(
        private readonly Clock $clock,
        private readonly Logger $logger,
        private readonly bool $enabled = true,
        private readonly int $sessionTimeout = 1800, // 30 minutes
        private array $sessionData = [],
        private array $behaviorPatterns = [],
        private array $timingThresholds = []
    ) {
        $this->initializeBehaviorPatterns();
        $this->initializeTimingThresholds();
    }

    /**
     * Analyze behavioral patterns for bot indicators
     */
    public function analyzeBehavior(RequestAnalysisData $requestData, array $context): BehavioralAnalysisResult
    {
        $startTime = $this->clock->time();

        if (! $this->enabled) {
            return BehavioralAnalysisResult::empty();
        }

        try {
            $sessionId = $this->getSessionId($requestData, $context);
            $detections = [];
            $behaviorMetrics = [];

            // Update session data
            $this->updateSessionData($sessionId, $requestData, $context);

            // Analyze request timing patterns
            $timingAnalysis = $this->analyzeRequestTiming($sessionId, $requestData, $context);
            if ($timingAnalysis['suspicious']) {
                $detections[] = new BotDetection(
                    type: BotDetectionType::TIMING_ANALYSIS,
                    severity: DetectionSeverity::MEDIUM,
                    message: $timingAnalysis['message'],
                    evidence: $timingAnalysis['evidence'],
                    confidence: Percentage::from($timingAnalysis['confidence']),
                    source: 'behavioral_detector'
                );
            }
            $behaviorMetrics['timing'] = $timingAnalysis['metrics'];

            // Analyze mouse movement patterns (if available)
            if (isset($context['mouse_movements'])) {
                $mouseAnalysis = $this->analyzeMouseMovement($context['mouse_movements']);
                if ($mouseAnalysis['suspicious']) {
                    $detections[] = new BotDetection(
                        type: BotDetectionType::MOUSE_MOVEMENT,
                        severity: DetectionSeverity::HIGH,
                        message: $mouseAnalysis['message'],
                        evidence: $mouseAnalysis['evidence'],
                        confidence: Percentage::from($mouseAnalysis['confidence']),
                        source: 'behavioral_detector'
                    );
                }
                $behaviorMetrics['mouse'] = $mouseAnalysis['metrics'];
            }

            // Analyze keystroke patterns (if available)
            if (isset($context['keystrokes'])) {
                $keystrokeAnalysis = $this->analyzeKeystrokePatterns($context['keystrokes']);
                if ($keystrokeAnalysis['suspicious']) {
                    $detections[] = new BotDetection(
                        type: BotDetectionType::KEYSTROKE_PATTERN,
                        severity: DetectionSeverity::MEDIUM,
                        message: $keystrokeAnalysis['message'],
                        evidence: $keystrokeAnalysis['evidence'],
                        confidence: Percentage::from($keystrokeAnalysis['confidence']),
                        source: 'behavioral_detector'
                    );
                }
                $behaviorMetrics['keystrokes'] = $keystrokeAnalysis['metrics'];
            }

            // Analyze navigation patterns
            $navigationAnalysis = $this->analyzeNavigationPatterns($sessionId);
            if ($navigationAnalysis['suspicious']) {
                $detections[] = new BotDetection(
                    type: BotDetectionType::BEHAVIORAL_ANOMALY,
                    severity: DetectionSeverity::MEDIUM,
                    message: $navigationAnalysis['message'],
                    evidence: $navigationAnalysis['evidence'],
                    confidence: Percentage::from($navigationAnalysis['confidence']),
                    source: 'behavioral_detector'
                );
            }
            $behaviorMetrics['navigation'] = $navigationAnalysis['metrics'];

            // Calculate overall risk score
            $riskScore = $this->calculateBehavioralRiskScore($detections, $behaviorMetrics);

            // Calculate confidence
            $confidence = $this->calculateAnalysisConfidence($detections, $behaviorMetrics);

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

            return new BehavioralAnalysisResult(
                isSuspicious: $riskScore > 50.0 || ! empty($detections),
                riskScore: $riskScore,
                detections: $detections,
                behaviorMetrics: $behaviorMetrics,
                analysisDuration: $analysisDuration,
                confidence: $confidence
            );

        } catch (\Throwable $e) {
            $this->logger->error('Behavioral analysis failed', [
                'error' => $e->getMessage(),
                'request_id' => $context['request_id'] ?? null,
            ]);

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

            return BehavioralAnalysisResult::empty();
        }
    }

    /**
     * Get session identifier
     */
    private function getSessionId(RequestAnalysisData $requestData, array $context): string
    {
        return hash('sha256', implode('|', [
            $requestData->clientIp?->toString() ?? '',
            $requestData->userAgent?->toString() ?? '',
            $context['session_id'] ?? '',
        ]));
    }

    /**
     * Update session behavioral data
     */
    private function updateSessionData(string $sessionId, RequestAnalysisData $requestData, array $context): void
    {
        $currentTime = $this->clock->time()->toUnixTimestamp();

        if (! isset($this->sessionData[$sessionId])) {
            $this->sessionData[$sessionId] = [
                'created_at' => $currentTime,
                'requests' => [],
                'mouse_movements' => [],
                'keystrokes' => [],
                'navigation_patterns' => [],
            ];
        }

        // Add current request
        $this->sessionData[$sessionId]['requests'][] = [
            'timestamp' => $currentTime,
            'path' => $requestData->path,
            'method' => $requestData->method,
            'user_agent' => $requestData->userAgent?->toString(),
            'referrer' => $requestData->headers['referer'] ?? null,
        ];

        // Add mouse movements if available
        if (isset($context['mouse_movements'])) {
            $this->sessionData[$sessionId]['mouse_movements'] = array_merge(
                $this->sessionData[$sessionId]['mouse_movements'],
                $context['mouse_movements']
            );
        }

        // Add keystrokes if available
        if (isset($context['keystrokes'])) {
            $this->sessionData[$sessionId]['keystrokes'] = array_merge(
                $this->sessionData[$sessionId]['keystrokes'],
                $context['keystrokes']
            );
        }

        // Limit session data size
        $this->limitSessionDataSize($sessionId);

        // Clean up old sessions
        $this->cleanupOldSessions();
    }

    /**
     * Analyze request timing patterns
     */
    private function analyzeRequestTiming(string $sessionId, RequestAnalysisData $requestData, array $context): array
    {
        $sessionRequests = $this->sessionData[$sessionId]['requests'] ?? [];

        if (count($sessionRequests) < 2) {
            return [
                'suspicious' => false,
                'message' => '',
                'evidence' => [],
                'confidence' => 0.0,
                'metrics' => ['request_count' => count($sessionRequests)],
            ];
        }

        // Calculate request intervals
        $intervals = [];
        for ($i = 1; $i < count($sessionRequests); $i++) {
            $intervals[] = $sessionRequests[$i]['timestamp'] - $sessionRequests[$i - 1]['timestamp'];
        }

        // Analyze interval patterns
        $avgInterval = array_sum($intervals) / count($intervals);
        $intervalVariance = $this->calculateVariance($intervals);

        $suspicious = false;
        $evidence = [];
        $confidence = 0.0;
        $message = '';

        // Check for suspiciously regular intervals (bot-like)
        if ($intervalVariance < 0.1 && count($intervals) >= 5) {
            $suspicious = true;
            $confidence = 85.0;
            $message = 'Highly regular request timing pattern detected';
            $evidence['interval_variance'] = $intervalVariance;
            $evidence['average_interval'] = $avgInterval;
        }

        // Check for suspiciously fast requests
        if ($avgInterval < $this->timingThresholds['min_human_interval']) {
            $suspicious = true;
            $confidence = max($confidence, 75.0);
            $message = 'Requests too fast for human interaction';
            $evidence['average_interval'] = $avgInterval;
            $evidence['threshold'] = $this->timingThresholds['min_human_interval'];
        }

        return [
            'suspicious' => $suspicious,
            'message' => $message,
            'evidence' => $evidence,
            'confidence' => $confidence,
            'metrics' => [
                'request_count' => count($sessionRequests),
                'average_interval' => $avgInterval,
                'interval_variance' => $intervalVariance,
                'intervals' => $intervals,
            ],
        ];
    }

    /**
     * Analyze mouse movement patterns
     */
    private function analyzeMouseMovement(array $movements): array
    {
        if (empty($movements)) {
            return [
                'suspicious' => false,
                'message' => '',
                'evidence' => [],
                'confidence' => 0.0,
                'metrics' => ['movement_count' => 0],
            ];
        }

        $suspicious = false;
        $evidence = [];
        $confidence = 0.0;
        $message = '';

        // Calculate movement metrics
        $totalDistance = 0;
        $totalTime = 0;
        $perfectLines = 0;

        for ($i = 1; $i < count($movements); $i++) {
            $prev = $movements[$i - 1];
            $curr = $movements[$i];

            $distance = sqrt(pow($curr['x'] - $prev['x'], 2) + pow($curr['y'] - $prev['y'], 2));
            $timeDiff = $curr['timestamp'] - $prev['timestamp'];

            $totalDistance += $distance;
            $totalTime += $timeDiff;

            // Check for perfectly straight lines (bot indicator)
            if ($distance > 10 && $this->isPerfectLine($prev, $curr)) {
                $perfectLines++;
            }
        }

        // Analyze patterns
        $avgSpeed = $totalTime > 0 ? $totalDistance / $totalTime : 0;
        $perfectLineRatio = count($movements) > 1 ? $perfectLines / (count($movements) - 1) : 0;

        // Check for bot-like patterns
        if ($perfectLineRatio > 0.8) {
            $suspicious = true;
            $confidence = 90.0;
            $message = 'Mouse movements are too perfect (likely automated)';
            $evidence['perfect_line_ratio'] = $perfectLineRatio;
        }

        if ($avgSpeed > 5000) { // Extremely fast movement
            $suspicious = true;
            $confidence = max($confidence, 80.0);
            $message = 'Mouse movement speed is inhuman';
            $evidence['average_speed'] = $avgSpeed;
        }

        return [
            'suspicious' => $suspicious,
            'message' => $message,
            'evidence' => $evidence,
            'confidence' => $confidence,
            'metrics' => [
                'movement_count' => count($movements),
                'total_distance' => $totalDistance,
                'average_speed' => $avgSpeed,
                'perfect_line_ratio' => $perfectLineRatio,
            ],
        ];
    }

    /**
     * Analyze keystroke patterns
     */
    private function analyzeKeystrokePatterns(array $keystrokes): array
    {
        if (count($keystrokes) < 5) {
            return [
                'suspicious' => false,
                'message' => '',
                'evidence' => [],
                'confidence' => 0.0,
                'metrics' => ['keystroke_count' => count($keystrokes)],
            ];
        }

        // Calculate keystroke intervals
        $intervals = [];
        for ($i = 1; $i < count($keystrokes); $i++) {
            $intervals[] = $keystrokes[$i]['timestamp'] - $keystrokes[$i - 1]['timestamp'];
        }

        $avgInterval = array_sum($intervals) / count($intervals);
        $intervalVariance = $this->calculateVariance($intervals);

        $suspicious = false;
        $evidence = [];
        $confidence = 0.0;
        $message = '';

        // Check for suspiciously regular keystroke timing
        if ($intervalVariance < 5 && count($intervals) >= 10) {
            $suspicious = true;
            $confidence = 85.0;
            $message = 'Keystroke timing is too regular (likely automated)';
            $evidence['interval_variance'] = $intervalVariance;
        }

        // Check for inhuman typing speed
        if ($avgInterval < 50) { // Less than 50ms between keystrokes
            $suspicious = true;
            $confidence = max($confidence, 90.0);
            $message = 'Typing speed is inhuman';
            $evidence['average_interval'] = $avgInterval;
        }

        return [
            'suspicious' => $suspicious,
            'message' => $message,
            'evidence' => $evidence,
            'confidence' => $confidence,
            'metrics' => [
                'keystroke_count' => count($keystrokes),
                'average_interval' => $avgInterval,
                'interval_variance' => $intervalVariance,
            ],
        ];
    }

    /**
     * Analyze navigation patterns
     */
    private function analyzeNavigationPatterns(string $sessionId): array
    {
        $requests = $this->sessionData[$sessionId]['requests'] ?? [];

        if (count($requests) < 3) {
            return [
                'suspicious' => false,
                'message' => '',
                'evidence' => [],
                'confidence' => 0.0,
                'metrics' => ['page_count' => count($requests)],
            ];
        }

        $suspicious = false;
        $evidence = [];
        $confidence = 0.0;
        $message = '';

        // Analyze path patterns
        $paths = array_column($requests, 'path');
        $uniquePaths = array_unique($paths);
        $pathRepetition = count($paths) > 0 ? (count($paths) - count($uniquePaths)) / count($paths) : 0;

        // Check for excessive path repetition
        if ($pathRepetition > 0.7) {
            $suspicious = true;
            $confidence = 70.0;
            $message = 'Excessive repetition in navigation patterns';
            $evidence['path_repetition_ratio'] = $pathRepetition;
        }

        // Check for missing referrers (common bot behavior)
        $missingReferrers = 0;
        foreach ($requests as $request) {
            if (empty($request['referrer'])) {
                $missingReferrers++;
            }
        }

        $missingReferrerRatio = count($requests) > 0 ? $missingReferrers / count($requests) : 0;
        if ($missingReferrerRatio > 0.8 && count($requests) >= 5) {
            $suspicious = true;
            $confidence = max($confidence, 60.0);
            $message = 'Unusual referrer pattern detected';
            $evidence['missing_referrer_ratio'] = $missingReferrerRatio;
        }

        return [
            'suspicious' => $suspicious,
            'message' => $message,
            'evidence' => $evidence,
            'confidence' => $confidence,
            'metrics' => [
                'page_count' => count($requests),
                'unique_pages' => count($uniquePaths),
                'path_repetition_ratio' => $pathRepetition,
                'missing_referrer_ratio' => $missingReferrerRatio,
            ],
        ];
    }

    /**
     * Calculate behavioral risk score
     */
    private function calculateBehavioralRiskScore(array $detections, array $behaviorMetrics): float
    {
        $score = 0.0;

        // Base score from detections
        foreach ($detections as $detection) {
            if ($detection instanceof BotDetection) {
                $score += $detection->getRiskContribution();
            }
        }

        // Additional scoring from metrics
        if (isset($behaviorMetrics['timing']['interval_variance'])) {
            $variance = $behaviorMetrics['timing']['interval_variance'];
            if ($variance < 0.1) {
                $score += 30; // Very regular timing
            }
        }

        if (isset($behaviorMetrics['mouse']['perfect_line_ratio'])) {
            $ratio = $behaviorMetrics['mouse']['perfect_line_ratio'];
            $score += $ratio * 40; // Perfect lines indicate automation
        }

        return min(100.0, $score);
    }

    /**
     * Calculate analysis confidence
     */
    private function calculateAnalysisConfidence(array $detections, array $behaviorMetrics): Percentage
    {
        if (empty($detections)) {
            return Percentage::from(0.0);
        }

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

        $avgConfidence = $totalConfidence / count($detections);

        // Boost confidence if multiple detection types agree
        if (count($detections) > 1) {
            $avgConfidence = min(100.0, $avgConfidence * 1.2);
        }

        return Percentage::from($avgConfidence);
    }

    /**
     * Check if line is perfectly straight
     */
    private function isPerfectLine(array $point1, array $point2): bool
    {
        // A perfectly straight line would have the same angle throughout
        // This is a simplified check - in reality, you'd need more sophisticated analysis
        return abs($point1['x'] - $point2['x']) < 2 || abs($point1['y'] - $point2['y']) < 2;
    }

    /**
     * Calculate variance of an array
     */
    private function calculateVariance(array $values): float
    {
        if (count($values) < 2) {
            return 0.0;
        }

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

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

    /**
     * Limit session data size to prevent memory issues
     */
    private function limitSessionDataSize(string $sessionId): void
    {
        $maxRequests = 100;
        $maxMovements = 1000;
        $maxKeystrokes = 500;

        if (isset($this->sessionData[$sessionId]['requests']) &&
            count($this->sessionData[$sessionId]['requests']) > $maxRequests) {
            $this->sessionData[$sessionId]['requests'] = array_slice(
                $this->sessionData[$sessionId]['requests'],
                -$maxRequests
            );
        }

        if (isset($this->sessionData[$sessionId]['mouse_movements']) &&
            count($this->sessionData[$sessionId]['mouse_movements']) > $maxMovements) {
            $this->sessionData[$sessionId]['mouse_movements'] = array_slice(
                $this->sessionData[$sessionId]['mouse_movements'],
                -$maxMovements
            );
        }

        if (isset($this->sessionData[$sessionId]['keystrokes']) &&
            count($this->sessionData[$sessionId]['keystrokes']) > $maxKeystrokes) {
            $this->sessionData[$sessionId]['keystrokes'] = array_slice(
                $this->sessionData[$sessionId]['keystrokes'],
                -$maxKeystrokes
            );
        }
    }

    /**
     * Clean up old session data
     */
    private function cleanupOldSessions(): void
    {
        $currentTime = $this->clock->time()->toUnixTimestamp();

        foreach ($this->sessionData as $sessionId => $data) {
            if ($currentTime - $data['created_at'] > $this->sessionTimeout) {
                unset($this->sessionData[$sessionId]);
            }
        }

        // Also limit total sessions to prevent memory issues
        if (count($this->sessionData) > 1000) {
            $sortedSessions = $this->sessionData;
            uasort($sortedSessions, fn ($a, $b) => $a['created_at'] <=> $b['created_at']);

            $toRemove = array_slice(array_keys($sortedSessions), 0, 500, true);
            foreach ($toRemove as $sessionId) {
                unset($this->sessionData[$sessionId]);
            }
        }
    }

    /**
     * Initialize behavior patterns
     */
    private function initializeBehaviorPatterns(): void
    {
        $this->behaviorPatterns = [
            'rapid_requests' => [
                'min_requests' => 10,
                'max_interval' => 1.0, // seconds
                'confidence' => 80.0,
            ],
            'regular_timing' => [
                'min_requests' => 5,
                'max_variance' => 0.1,
                'confidence' => 85.0,
            ],
        ];
    }

    /**
     * Initialize timing thresholds
     */
    private function initializeTimingThresholds(): void
    {
        $this->timingThresholds = [
            'min_human_interval' => 0.5, // Minimum seconds between human requests
            'max_human_speed' => 2000, // Maximum mouse movement speed (pixels/second)
            'min_keystroke_interval' => 50, // Minimum milliseconds between keystrokes
        ];
    }

    /**
     * Get detector statistics
     */
    public function getStatistics(): array
    {
        return [
            'enabled' => $this->enabled,
            'active_sessions' => count($this->sessionData),
            'session_timeout_seconds' => $this->sessionTimeout,
            'behavior_patterns_loaded' => count($this->behaviorPatterns),
        ];
    }

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