<?php

declare(strict_types=1);

namespace App\Framework\Waf\BotProtection\Detectors;

use App\Framework\DateTime\Clock;
use App\Framework\Logging\Logger;
use App\Framework\Waf\Analysis\ValueObjects\RequestAnalysisData;
use App\Framework\Waf\BotProtection\ValueObjects\DeviceProfile;

/**
 * Device intelligence detector for automated systems
 */
final class DeviceIntelligenceDetector
{
    public function __construct(
        private readonly Clock $clock,
        private readonly Logger $logger,
        private readonly bool $enabled = true,
        private readonly int $profileCacheTimeout = 1800, // 30 minutes
        private array $deviceProfileCache = [],
        private array $suspiciousDevices = [],
        private array $devicePatterns = []
    ) {
        $this->initializeDevicePatterns();
    }

    /**
     * Analyze device characteristics for bot indicators
     */
    public function analyzeDevice(RequestAnalysisData $requestData, array $context): DeviceProfile
    {
        if (! $this->enabled) {
            return $this->createEmptyProfile();
        }

        try {
            // Extract device data from request and context
            $deviceData = $this->extractDeviceData($requestData, $context);

            // Check cache first
            $cacheKey = $this->generateProfileCacheKey($deviceData);
            if (isset($this->deviceProfileCache[$cacheKey])) {
                $cached = $this->deviceProfileCache[$cacheKey];
                if ($this->clock->time()->toUnixTimestamp() - $cached['timestamp'] < $this->profileCacheTimeout) {
                    return $cached['profile'];
                }
            }

            // Create device profile
            $profile = DeviceProfile::fromData($deviceData, $this->clock->time());

            // Additional analysis
            $this->analyzeUserAgentConsistency($profile, $deviceData);
            $this->analyzeHardwareConsistency($profile, $deviceData);
            $this->checkAgainstKnownPatterns($profile, $deviceData);

            // Cache the result
            $this->deviceProfileCache[$cacheKey] = [
                'profile' => $profile,
                'timestamp' => $this->clock->time()->toUnixTimestamp(),
            ];

            // Track suspicious devices
            if ($profile->isSuspicious()) {
                $this->trackSuspiciousDevice($profile, $context);
            }

            // Limit cache size
            if (count($this->deviceProfileCache) > 5000) {
                $this->cleanupProfileCache();
            }

            return $profile;

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

            return $this->createEmptyProfile();
        }
    }

    /**
     * Extract device data from request and context
     */
    private function extractDeviceData(RequestAnalysisData $requestData, array $context): array
    {
        $userAgent = $requestData->userAgent?->toString() ?? '';

        $data = [
            'user_agent' => $userAgent,
            'os' => $this->extractOperatingSystem($userAgent),
            'browser' => $this->extractBrowser($userAgent),
            'device_type' => $this->extractDeviceType($userAgent),
            'language' => $requestData->headers['accept-language'] ?? null,
        ];

        // Extract client-provided device information
        if (isset($context['device_info'])) {
            $deviceInfo = $context['device_info'];

            // Hardware specifications
            if (isset($deviceInfo['hardware'])) {
                $data['hardware'] = [
                    'screen_width' => $deviceInfo['hardware']['screen_width'] ?? null,
                    'screen_height' => $deviceInfo['hardware']['screen_height'] ?? null,
                    'device_pixel_ratio' => $deviceInfo['hardware']['device_pixel_ratio'] ?? null,
                    'color_depth' => $deviceInfo['hardware']['color_depth'] ?? null,
                ];
            }

            // Memory information
            $data['memory_size'] = $deviceInfo['memory'] ?? null;

            // CPU information
            $data['cpu_cores'] = $deviceInfo['cpu_cores'] ?? null;

            // GPU information
            $data['gpu_renderer'] = $deviceInfo['gpu_renderer'] ?? null;

            // Network information
            if (isset($deviceInfo['network'])) {
                $data['network'] = [
                    'connection_type' => $deviceInfo['network']['type'] ?? null,
                    'downlink' => $deviceInfo['network']['downlink'] ?? null,
                    'rtt' => $deviceInfo['network']['rtt'] ?? null,
                ];
            }

            // Location/timezone
            if (isset($deviceInfo['location'])) {
                $data['location'] = [
                    'timezone_offset' => $deviceInfo['location']['timezone_offset'] ?? null,
                    'timezone_name' => $deviceInfo['location']['timezone_name'] ?? null,
                ];
            }

            // Battery status
            $data['battery_level'] = $deviceInfo['battery_level'] ?? null;

            // Connection type
            if (isset($deviceInfo['connection'])) {
                $data['connection_type'] = [
                    'type' => $deviceInfo['connection']['type'] ?? null,
                    'speed' => $deviceInfo['connection']['speed'] ?? null,
                ];
            }

            // Automation indicators
            $data['is_vm'] = $this->detectVirtualMachine($deviceInfo, $userAgent);
            $data['is_headless'] = $this->detectHeadlessBrowser($deviceInfo, $userAgent);
            $data['has_webdriver'] = $this->detectWebDriver($deviceInfo, $context);
        }

        return $data;
    }

    /**
     * Extract operating system from user agent
     */
    private function extractOperatingSystem(string $userAgent): ?string
    {
        $patterns = [
            'Windows NT 10.0' => 'Windows 10',
            'Windows NT 6.3' => 'Windows 8.1',
            'Windows NT 6.2' => 'Windows 8',
            'Windows NT 6.1' => 'Windows 7',
            'Mac OS X' => 'macOS',
            'iPhone OS' => 'iOS',
            'Android' => 'Android',
            'Linux' => 'Linux',
            'Ubuntu' => 'Ubuntu',
        ];

        foreach ($patterns as $pattern => $os) {
            if (stripos($userAgent, $pattern) !== false) {
                return $os;
            }
        }

        return null;
    }

    /**
     * Extract browser from user agent
     */
    private function extractBrowser(string $userAgent): ?string
    {
        $patterns = [
            'Chrome' => 'Chrome',
            'Firefox' => 'Firefox',
            'Safari' => 'Safari',
            'Edge' => 'Edge',
            'Opera' => 'Opera',
            'Internet Explorer' => 'Internet Explorer',
        ];

        foreach ($patterns as $pattern => $browser) {
            if (stripos($userAgent, $pattern) !== false) {
                return $browser;
            }
        }

        return null;
    }

    /**
     * Extract device type from user agent
     */
    private function extractDeviceType(string $userAgent): string
    {
        if (preg_match('/(tablet|ipad)/i', $userAgent)) {
            return 'tablet';
        }

        if (preg_match('/(mobile|phone|android|ios)/i', $userAgent)) {
            return 'mobile';
        }

        return 'desktop';
    }

    /**
     * Detect virtual machine environment
     */
    private function detectVirtualMachine(array $deviceInfo, string $userAgent): bool
    {
        // Check for VM indicators in user agent
        $vmIndicators = ['VirtualBox', 'VMware', 'QEMU', 'Parallels', 'Xen'];
        foreach ($vmIndicators as $indicator) {
            if (stripos($userAgent, $indicator) !== false) {
                return true;
            }
        }

        // Check for VM indicators in device info
        if (isset($deviceInfo['gpu_renderer'])) {
            $vmGpuPatterns = ['VirtualBox', 'VMware', 'Microsoft Basic', 'VirtIO'];
            foreach ($vmGpuPatterns as $pattern) {
                if (stripos($deviceInfo['gpu_renderer'], $pattern) !== false) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Detect headless browser
     */
    private function detectHeadlessBrowser(array $deviceInfo, string $userAgent): bool
    {
        // Check for headless indicators in user agent
        $headlessIndicators = ['HeadlessChrome', 'PhantomJS', 'SlimerJS'];
        foreach ($headlessIndicators as $indicator) {
            if (stripos($userAgent, $indicator) !== false) {
                return true;
            }
        }

        // Check for missing expected features
        if (isset($deviceInfo['hardware'])) {
            $hardware = $deviceInfo['hardware'];
            // Headless browsers often report 0x0 screen dimensions
            if (($hardware['screen_width'] ?? 0) === 0 || ($hardware['screen_height'] ?? 0) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Detect WebDriver automation
     */
    private function detectWebDriver(array $deviceInfo, array $context): bool
    {
        // Check for WebDriver properties in JavaScript context
        if (isset($context['webdriver_detected'])) {
            return $context['webdriver_detected'];
        }

        // Check for WebDriver indicators in request headers
        $headers = $context['headers'] ?? [];
        foreach ($headers as $name => $value) {
            if (stripos($name, 'webdriver') !== false || stripos($value, 'webdriver') !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Analyze user agent consistency with device characteristics
     */
    private function analyzeUserAgentConsistency(DeviceProfile $profile, array $deviceData): void
    {
        // This would analyze if the user agent matches the reported device characteristics
        // For example, mobile user agent with desktop screen resolution
    }

    /**
     * Analyze hardware consistency
     */
    private function analyzeHardwareConsistency(DeviceProfile $profile, array $deviceData): void
    {
        // This would analyze if hardware specifications are consistent
        // For example, high-end CPU with low memory, or missing expected mobile features
    }

    /**
     * Check device against known suspicious patterns
     */
    private function checkAgainstKnownPatterns(DeviceProfile $profile, array $deviceData): void
    {
        foreach ($this->devicePatterns as $pattern) {
            if ($this->matchesPattern($deviceData, $pattern)) {
                // Add to suspicious features or increase suspicion score
            }
        }
    }

    /**
     * Check if device data matches a suspicious pattern
     */
    private function matchesPattern(array $deviceData, array $pattern): bool
    {
        foreach ($pattern as $key => $expectedValue) {
            if (! isset($deviceData[$key]) || $deviceData[$key] !== $expectedValue) {
                return false;
            }
        }

        return true;
    }

    /**
     * Track suspicious device for analysis
     */
    private function trackSuspiciousDevice(DeviceProfile $profile, array $context): void
    {
        $deviceId = $profile->deviceId;

        if (! isset($this->suspiciousDevices[$deviceId])) {
            $this->suspiciousDevices[$deviceId] = [
                'first_seen' => $this->clock->time()->toUnixTimestamp(),
                'detection_count' => 0,
                'highest_suspicion_score' => 0.0,
            ];
        }

        $this->suspiciousDevices[$deviceId]['detection_count']++;
        $this->suspiciousDevices[$deviceId]['last_seen'] = $this->clock->time()->toUnixTimestamp();
        $this->suspiciousDevices[$deviceId]['highest_suspicion_score'] = max(
            $this->suspiciousDevices[$deviceId]['highest_suspicion_score'],
            $profile->getSuspicionScore()
        );

        // Limit tracking to most recent suspicious devices
        if (count($this->suspiciousDevices) > 1000) {
            $oldest = array_keys($this->suspiciousDevices)[0];
            unset($this->suspiciousDevices[$oldest]);
        }
    }

    /**
     * Initialize known suspicious device patterns
     */
    private function initializeDevicePatterns(): void
    {
        $this->devicePatterns = [
            // Common automation frameworks
            [
                'browser' => 'Chrome',
                'is_headless' => true,
                'has_webdriver' => true,
            ],
            // Virtual machine with automation
            [
                'is_vm' => true,
                'has_webdriver' => true,
            ],
            // Suspicious hardware combinations
            [
                'memory_size' => 256, // Very low memory
                'cpu_cores' => 1,
            ],
        ];
    }

    /**
     * Create empty device profile for disabled/error cases
     */
    private function createEmptyProfile(): DeviceProfile
    {
        return DeviceProfile::fromData([], $this->clock->time());
    }

    /**
     * Generate cache key for device profile
     */
    private function generateProfileCacheKey(array $deviceData): string
    {
        return hash('sha256', json_encode($deviceData));
    }

    /**
     * Clean up old profile cache entries
     */
    private function cleanupProfileCache(): void
    {
        $currentTime = $this->clock->time()->toUnixTimestamp();

        foreach ($this->deviceProfileCache as $key => $entry) {
            if ($currentTime - $entry['timestamp'] > $this->profileCacheTimeout) {
                unset($this->deviceProfileCache[$key]);
            }
        }

        // If still too large, remove oldest entries
        if (count($this->deviceProfileCache) > 2500) {
            $sorted = $this->deviceProfileCache;
            uasort($sorted, fn ($a, $b) => $a['timestamp'] <=> $b['timestamp']);

            $toRemove = array_slice(array_keys($sorted), 0, 1250, true);
            foreach ($toRemove as $key) {
                unset($this->deviceProfileCache[$key]);
            }
        }
    }

    /**
     * Get detector statistics
     */
    public function getStatistics(): array
    {
        return [
            'enabled' => $this->enabled,
            'profile_cache_size' => count($this->deviceProfileCache),
            'cache_timeout_seconds' => $this->profileCacheTimeout,
            'suspicious_devices_tracked' => count($this->suspiciousDevices),
            'device_patterns_loaded' => count($this->devicePatterns),
        ];
    }

    /**
     * Get suspicious devices
     */
    public function getSuspiciousDevices(): array
    {
        return $this->suspiciousDevices;
    }

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