<?php

declare(strict_types=1);

namespace App\Framework\Discovery;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Config\AppConfig;
use App\Framework\Config\DiscoveryConfig;
use App\Framework\Config\TypedConfiguration;
use App\Framework\Context\ExecutionContext;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\DI\Container;
use App\Framework\Discovery\Cache\DiscoveryCacheIdentifiers;
use App\Framework\Discovery\Factory\DiscoveryServiceFactory;
use App\Framework\Discovery\Results\DiscoveryRegistry;

/**
 * Bootstrapper für den Discovery-Service
 * Ersetzt die alte AttributeDiscoveryService-Integration
 */
final readonly class DiscoveryServiceBootstrapper
{
    public function __construct(
        private Container $container,
        private Clock $clock
    ) {
    }

    /**
     * Bootstrapt den Discovery-Service und führt die Discovery durch
     */
    public function bootstrap(): DiscoveryRegistry
    {
        $pathProvider = $this->container->get(PathProvider::class);
        $cache = $this->container->get(Cache::class);

        // Get DiscoveryConfig if available, otherwise use defaults
        $discoveryConfig = null;
        if ($this->container->has(DiscoveryConfig::class)) {
            $discoveryConfig = $this->container->get(DiscoveryConfig::class);
        }

        // Context-spezifische Discovery mit separaten Caches
        $currentContext = ExecutionContext::detect();
        $contextString = $currentContext->getType()->value;

        // Direkter Cache-Check mit expliziter toArray/fromArray Serialisierung
        $defaultPaths = [$pathProvider->getSourcePath()];
        $cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($defaultPaths, $contextString);


        $cachedItem = $cache->get($cacheKey);

        if ($cachedItem->isHit) {
            // Ensure DiscoveryRegistry class is loaded before attempting deserialization
            if (! class_exists(DiscoveryRegistry::class, true)) {
                $cachedRegistry = null;
            } else {
                // Versuche die gecachten Daten zu laden
                $cachedRegistry = null;

                try {
                    // Skip incomplete classes - they indicate autoloader issues
                    if (is_object($cachedItem->value) && get_class($cachedItem->value) === '__PHP_Incomplete_Class') {
                        $cachedRegistry = null;
                    } elseif ($cachedItem->value instanceof DiscoveryRegistry) {
                        $cachedRegistry = $cachedItem->value;
                    } elseif (is_array($cachedItem->value)) {
                        $cachedRegistry = DiscoveryRegistry::fromArray($cachedItem->value);
                    } elseif (is_string($cachedItem->value)) {
                        $cachedRegistry = DiscoveryRegistry::fromArray(json_decode($cachedItem->value, true, 512, JSON_THROW_ON_ERROR));
                    } else {
                        $cachedRegistry = null;
                    }
                } catch (\Throwable $e) {
                    $cachedRegistry = null;
                }
            }

            if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) {
                $this->container->singleton(DiscoveryRegistry::class, $cachedRegistry);

                // Process DefaultImplementation attributes first (before Initializers)
                $defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class);
                $defaultImplProcessor->process($cachedRegistry);

                // Initializer-Verarbeitung für gecachte Registry
                $initializerProcessor = $this->container->get(InitializerProcessor::class);
                $initializerProcessor->processInitializers($cachedRegistry);

                return $cachedRegistry;
            }
        }

        // Fallback: Vollständige Discovery durchführen
        $results = $this->performBootstrap($pathProvider, $cache, $discoveryConfig);

        // Nach der Discovery explizit in unserem eigenen Cache-Format speichern
        $consoleCommandCount = count($results->attributes->get(\App\Framework\Console\ConsoleCommand::class));

        // Only cache if we found meaningful results
        // An empty discovery likely indicates initialization timing issues
        if (! $results->isEmpty() && $consoleCommandCount > 0) {
            $arrayData = $results->toArray();

            $cacheItem = CacheItem::forSet(
                key: $cacheKey,
                value: $arrayData,
                ttl: Duration::fromHours(1)
            );

            $cache->set($cacheItem);
        }

        return $results;
    }

    /**
     * Führt den Discovery-Prozess durch und verarbeitet die Ergebnisse
     */
    public function performBootstrap(PathProvider $pathProvider, Cache $cache, ?DiscoveryConfig $config): DiscoveryRegistry
    {
        // Context-spezifische Discovery
        $currentContext = ExecutionContext::detect();
        $discoveryService = $this->createDiscoveryService($pathProvider, $cache, $config, $currentContext);

        // Discovery durchführen
        $results = $discoveryService->discover();

        // Ergebnisse im Container registrieren
        $this->container->singleton(DiscoveryRegistry::class, $results);
        $this->container->instance(UnifiedDiscoveryService::class, $discoveryService);

        // Process DefaultImplementation attributes first (before Initializers, as they may depend on them)
        $defaultImplProcessor = $this->container->get(\App\Framework\DI\DefaultImplementationProcessor::class);
        $defaultImplProcessor->process($results);

        // Initializer-Verarbeitung mit dedizierter Klasse
        $initializerProcessor = $this->container->get(InitializerProcessor::class);
        $initializerProcessor->processInitializers($results);

        return $results;
    }

    /**
     * Creates the modern discovery service with enhanced features using the new factory
     */
    private function createDiscoveryService(
        PathProvider $pathProvider,
        Cache $cache,
        ?DiscoveryConfig $config,
        ?ExecutionContext $context = null
    ): UnifiedDiscoveryService {
        // Create factory
        $factory = new DiscoveryServiceFactory(
            $this->container,
            $pathProvider,
            $cache,
            $this->clock
        );

        // Determine environment-based factory method
        $envType = 'production'; // Default fallback

        if ($this->container->has(AppConfig::class)) {
            $appConfig = $this->container->get(AppConfig::class);
            $envType = $appConfig->environment;
        } elseif ($this->container->has(TypedConfiguration::class)) {
            $typedConfig = $this->container->get(TypedConfiguration::class);
            $appConfigFromTyped = $typedConfig->appConfig();
            $envType = is_string($appConfigFromTyped->environment)
                ? $appConfigFromTyped->environment
                : $appConfigFromTyped->environment->value;
        }

        // Use factory methods which include default paths
        // For console/CLI contexts, always use development configuration
        // to ensure consistent discovery behavior across CLI environments
        $currentContext = $context?->getType();

        return match (true) {
            // CLI contexts (console, cli-script, test) should use development config
            // to ensure consistent command discovery
            $currentContext?->value === 'console' => $factory->createForDevelopment(),
            $currentContext?->value === 'cli-script' => $factory->createForDevelopment(),
            $currentContext?->value === 'test' => $factory->createForDevelopment(),

            // Environment-based fallback for non-CLI contexts
            $envType === 'development' => $factory->createForDevelopment(),
            $envType === 'testing' => $factory->createForDevelopment(), // Use development config for testing too
            default => $factory->createForProduction()
        };
    }

    /**
     * Führt einen inkrementellen Discovery-Scan durch und verarbeitet die Ergebnisse
     * mit context-aware Initializer-Verarbeitung
     */
    public function incrementalBootstrap(): DiscoveryRegistry
    {
        if (! $this->container->has(UnifiedDiscoveryService::class)) {
            // Fallback auf vollständigen Bootstrap
            return $this->bootstrap();
        }

        $discoveryService = $this->container->get(UnifiedDiscoveryService::class);
        $results = $discoveryService->incrementalDiscover();

        // Aktualisiere Container
        $this->container->instance(DiscoveryRegistry::class, $results);

        // Re-process initializers for current context
        $initializerProcessor = $this->container->get(InitializerProcessor::class);
        $initializerProcessor->processInitializers($results);

        return $results;
    }

    /**
     * Hilfsmethode um zu prüfen, ob Discovery erforderlich ist
     */
    public function isDiscoveryRequired(): bool
    {
        if (! $this->container->has(UnifiedDiscoveryService::class)) {
            return true;
        }

        $discoveryService = $this->container->get(UnifiedDiscoveryService::class);

        return $discoveryService->shouldRescan();
    }
}
