<?php

declare(strict_types=1);

namespace App\Framework\UserAgent;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\Core\ValueObjects\HashAlgorithm;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\UserAgent\Enums\BrowserType;
use App\Framework\UserAgent\Enums\EngineType;
use App\Framework\UserAgent\Enums\PlatformType;
use App\Framework\UserAgent\Patterns\BrowserPatterns;
use App\Framework\UserAgent\Patterns\EnginePatterns;
use App\Framework\UserAgent\Patterns\PlatformPatterns;

/**
 * High-performance User-Agent parser with caching
 * Optimized for modern browsers and frameworks
 */
final readonly class UserAgentParser
{
    public function __construct(
        private ?Cache $cache = null
    ) {
    }

    /**
     * Parse User-Agent string into structured ParsedUserAgent object
     */
    public function parse(string $userAgentString): ParsedUserAgent
    {
        $normalized = trim($userAgentString);

        // Handle empty User-Agent
        if ($normalized === '') {
            return $this->createUnknownUserAgent('');
        }

        // Check cache first (using framework's Hash VO with fast algorithm)
        $hash = Hash::create($normalized, HashAlgorithm::fast());
        $cacheKey = CacheKey::fromString('useragent:' . $hash->toString());
        if ($this->cache) {
            $cached = $this->cache->get($cacheKey);
            if ($cached instanceof ParsedUserAgent) {
                return $cached;
            }
        }

        // Parse components
        $browser = $this->parseBrowser($normalized);
        $browserVersion = $this->parseBrowserVersion($normalized, $browser);
        $platform = $this->parsePlatform($normalized);
        $platformVersion = $this->parsePlatformVersion($normalized, $platform);
        $engine = $this->parseEngine($normalized, $browser);
        $engineVersion = $this->parseEngineVersion($normalized, $engine);
        $isMobile = PlatformPatterns::isMobile($normalized) || $platform->isMobile();
        $isBot = BrowserPatterns::isBot($normalized);
        $isModern = $this->determineModernBrowser($browser, $browserVersion, $isBot);

        $parsedUserAgent = new ParsedUserAgent(
            raw: $normalized,
            browser: $browser,
            browserVersion: $browserVersion,
            platform: $platform,
            platformVersion: $platformVersion,
            engine: $engine,
            engineVersion: $engineVersion,
            isMobile: $isMobile,
            isBot: $isBot,
            isModern: $isModern
        );

        // Cache result for 1 hour
        if ($this->cache) {
            $this->cache->set($cacheKey, $parsedUserAgent, Duration::fromHours(1));
        }

        return $parsedUserAgent;
    }

    /**
     * Parse browser type and version
     */
    private function parseBrowser(string $userAgent): BrowserType
    {
        foreach (BrowserPatterns::getPatterns() as $pattern) {
            if (preg_match($pattern['pattern'], $userAgent)) {
                return $pattern['browser'];
            }
        }

        // Try fallback patterns
        foreach (BrowserPatterns::getFallbackPatterns() as $fallback) {
            if (preg_match($fallback['pattern'], $userAgent)) {
                return $fallback['browser'];
            }
        }

        return BrowserType::UNKNOWN;
    }

    /**
     * Parse browser version
     */
    private function parseBrowserVersion(string $userAgent, BrowserType $browser): Version
    {
        // Find matching pattern for this browser
        foreach (BrowserPatterns::getPatterns() as $pattern) {
            if ($pattern['browser'] === $browser && preg_match($pattern['versionPattern'], $userAgent, $matches)) {
                $versionString = $matches[1] ?? '0.0.0';

                return $this->parseVersion($versionString);
            }
        }

        return Version::fromString('0.0.0');
    }

    /**
     * Parse platform/operating system
     */
    private function parsePlatform(string $userAgent): PlatformType
    {
        foreach (PlatformPatterns::getPatterns() as $pattern) {
            if (preg_match($pattern['pattern'], $userAgent)) {
                return $pattern['platform'];
            }
        }

        return PlatformType::UNKNOWN;
    }

    /**
     * Parse platform version
     */
    private function parsePlatformVersion(string $userAgent, PlatformType $platform): Version
    {
        foreach (PlatformPatterns::getPatterns() as $pattern) {
            if ($pattern['platform'] === $platform &&
                ! empty($pattern['versionPattern']) &&
                preg_match($pattern['versionPattern'], $userAgent, $matches)) {

                $version = $matches[1] ?? '0.0.0';

                // Format version based on platform
                $formattedVersion = match ($platform) {
                    PlatformType::WINDOWS => PlatformPatterns::formatWindowsVersion($version),
                    PlatformType::MACOS, PlatformType::IOS => PlatformPatterns::formatAppleVersion($version),
                    default => $version
                };

                return $this->parseVersion($formattedVersion);
            }
        }

        return Version::fromString('0.0.0');
    }

    /**
     * Parse browser engine
     */
    private function parseEngine(string $userAgent, BrowserType $browser): EngineType
    {
        // Try pattern-based detection first
        foreach (EnginePatterns::getPatterns() as $pattern) {
            if (preg_match($pattern['pattern'], $userAgent)) {
                return $pattern['engine'];
            }
        }

        // Fallback to browser-based mapping
        $engineMap = EnginePatterns::getBrowserEngineMap();

        return $engineMap[$browser->value] ?? EngineType::UNKNOWN;
    }

    /**
     * Parse engine version
     */
    private function parseEngineVersion(string $userAgent, EngineType $engine): Version
    {
        foreach (EnginePatterns::getPatterns() as $pattern) {
            if ($pattern['engine'] === $engine && preg_match($pattern['versionPattern'], $userAgent, $matches)) {
                $version = $matches[1] ?? '0.0.0';

                // Special formatting for Gecko
                if ($engine === EngineType::GECKO) {
                    $formattedVersion = EnginePatterns::formatGeckoVersion($version);

                    return $this->parseVersion($formattedVersion);
                }

                return $this->parseVersion($version);
            }
        }

        return Version::fromString('0.0.0');
    }

    /**
     * Determine if browser is considered modern
     */
    private function determineModernBrowser(BrowserType $browser, Version $version, bool $isBot): bool
    {
        if ($isBot) {
            return false;
        }

        if (! $browser->isModern()) {
            return false;
        }

        $threshold = Version::fromString($browser->getModernVersionThreshold());

        return $version->isNewerThan($threshold) || $version->equals($threshold);
    }

    /**
     * Create unknown ParsedUserAgent object
     */
    private function createUnknownUserAgent(string $raw): ParsedUserAgent
    {
        return new ParsedUserAgent(
            raw: $raw,
            browser: BrowserType::UNKNOWN,
            browserVersion: Version::fromString('0.0.0'),
            platform: PlatformType::UNKNOWN,
            platformVersion: Version::fromString('0.0.0'),
            engine: EngineType::UNKNOWN,
            engineVersion: Version::fromString('0.0.0'),
            isMobile: false,
            isBot: false,
            isModern: false
        );
    }

    /**
     * Parse version string into Version Value Object
     * Handles various version formats from User-Agent strings
     */
    private function parseVersion(string $versionString): Version
    {
        // Normalize version string
        $normalized = trim($versionString);

        if ($normalized === '' || $normalized === 'Unknown') {
            return Version::fromString('0.0.0');
        }

        // Try to parse as semver
        try {
            return Version::fromString($normalized);
        } catch (\InvalidArgumentException $e) {
            // If parsing fails, try to extract major.minor.patch from string
            if (preg_match('/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/', $normalized, $matches)) {
                $major = (int) $matches[1];
                $minor = isset($matches[2]) ? (int) $matches[2] : 0;
                $patch = isset($matches[3]) ? (int) $matches[3] : 0;

                return Version::fromComponents($major, $minor, $patch);
            }

            // Fallback to 0.0.0 if we can't parse
            return Version::fromString('0.0.0');
        }
    }

    /**
     * Clear parser cache
     */
    public function clearCache(): void
    {
        if ($this->cache) {
            // Clear all useragent cache entries
            // This would need cache implementation that supports key patterns
            // For now, this is a placeholder
        }
    }

    /**
     * Get parser statistics (for debugging/monitoring)
     * @return array<string, mixed>
     */
    public function getStats(): array
    {
        return [
            'cacheEnabled' => $this->cache !== null,
            'supportedBrowsers' => count(BrowserType::cases()),
            'supportedPlatforms' => count(PlatformType::cases()),
            'supportedEngines' => count(EngineType::cases()),
        ];
    }
}
