<?php

declare(strict_types=1);

namespace App\Framework\Discovery;

use App\Framework\Context\ContextType;
use App\Framework\Context\ExecutionContext;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\Container;
use App\Framework\DI\Initializer;
use App\Framework\DI\InitializerDependencyGraph;
use App\Framework\DI\ValueObjects\DependencyGraphNode;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Reflection\ReflectionProvider;

/**
 * Processes and executes Initializers discovered by the Discovery system
 *
 * Handles context filtering, dependency graph construction, and lazy service registration.
 * Separated from DiscoveryServiceBootstrapper for Single Responsibility Principle.
 */
final readonly class InitializerProcessor
{
    public function __construct(
        private Container $container,
        private ReflectionProvider $reflectionProvider,
        private ExecutionContext $executionContext,
    ) {
    }

    /**
     * Intelligente Initializer-Verarbeitung mit Dependency-Graph:
     * - Context-Filter: Nur für passende Execution-Contexts
     * - void/null Return: Sofort ausführen (Setup)
     * - Konkreter Return-Type: Dependency-Graph basierte Registrierung
     */
    public function processInitializers(DiscoveryRegistry $results): void
    {
        // Safe Logger resolution - use if available, otherwise rely on error_log
        $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
        $initializerResults = $results->attributes->get(Initializer::class);

        $logger?->debug("InitializerProcessor: Processing " . count($initializerResults) . " initializers");

        $dependencyGraph = new InitializerDependencyGraph($this->reflectionProvider);

        // Phase 1: Setup-Initializer sofort ausführen & Service-Initializer zum Graph hinzufügen
        foreach ($initializerResults as $discoveredAttribute) {

            /** @var Initializer $initializer */
            $initializer = $discoveredAttribute->createAttributeInstance();

            // The actual initializer data is in the additionalData from the InitializerMapper
            $initializerData = $discoveredAttribute->additionalData;

            if (! $initializerData) {
                continue;
            }

            // Context-Filter: Prüfe ob Initializer für aktuellen Context erlaubt ist
            // The contexts data is directly in the additionalData
            // Wenn contexts null ist, ist der Initializer für alle Contexts verfügbar
            if (isset($initializerData['contexts']) && $initializerData['contexts'] !== null && ! $this->isContextAllowed(...$initializerData['contexts'])) {
                continue;
            }

            $methodName = $discoveredAttribute->methodName ?? MethodName::invoke();
            // The return type is directly in the additionalData from the InitializerMapper
            $returnType = $initializerData['return'] ?? null;

            try {
                // Setup-Initializer: void/null Return → Sofort ausführen
                if ($returnType === null || $returnType === 'void') {
                    $this->container->invoker->invoke($discoveredAttribute->className, $methodName->toString());
                }
                // Service-Initializer: Konkreter Return-Type → Zum Dependency-Graph hinzufügen
                else {
                    $dependencyGraph->addInitializer($returnType, $discoveredAttribute->className, $methodName);
                }
            } catch (\Throwable $e) {
                $logger?->error(
                    "Failed to register initializer: {$discoveredAttribute->className->toString()}::{$methodName->toString()}",
                    LogContext::withExceptionAndData($e, [
                        'class' => $discoveredAttribute->className->toString(),
                        'method' => $methodName->toString(),
                        'return_type' => $returnType,
                    ])
                );

                // Skip failed initializers to prevent breaking the entire application
            }
        }

        // Phase 2: Service-Initializer in optimaler Reihenfolge registrieren
        $this->registerServicesWithDependencyGraph($dependencyGraph);
    }

    /**
     * Registriert Services basierend auf Dependency-Graph in optimaler Reihenfolge
     */
    private function registerServicesWithDependencyGraph(InitializerDependencyGraph $graph): void
    {
        try {
            $executionOrder = $graph->getExecutionOrder();

            foreach ($executionOrder as $returnType) {

                if ($graph->hasNode($returnType)) {
                    /** @var DependencyGraphNode $node */
                    $node = $graph->getNode($returnType);



                    $this->registerLazyService(
                        $returnType,
                        $node->getClassName(),
                        $node->getMethodName()
                    );
                }
            }
        } catch (\Throwable $e) {
            // Fallback: Registriere alle Services ohne spezielle Reihenfolge
            /** @var string $returnType */
            foreach ($graph->getNodes() as $returnType => $node) {
                $this->registerLazyService(
                    $returnType,
                    $node->getClassName(),
                    $node->getMethodName()
                );
            }
        }

        unset($graph);
    }

    /**
     * Prüft ob ein Initializer im aktuellen Context ausgeführt werden darf
     */
    private function isContextAllowed(mixed ...$contexts): bool
    {
        $currentContext = $this->executionContext->getType();

        foreach ($contexts as $context) {
            // Handle both ContextType objects and string values (from cache deserialization)
            if ($context instanceof ContextType) {
                if ($currentContext === $context) {
                    return true;
                }
            } elseif (is_string($context)) {
                if ($currentContext->value === $context) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Registriert einen Service lazy im Container mit Dual-Registration für Interfaces
     */
    private function registerLazyService(string $returnType, string $className, string $methodName): void
    {

        $factory = function ($container) use ($className, $methodName, $returnType) {

            $instance = $container->invoker->invoke(ClassName::create($className), $methodName);

            // Wenn das ein Interface ist, registriere auch die konkrete Klasse automatisch
            if (interface_exists($returnType)) {
                $concreteClass = get_class($instance);
                if (! $container->has($concreteClass)) {
                    $container->instance($concreteClass, $instance);
                }
            }

            return $instance;
        };

        try {
            // Registriere den Return-Type (Interface oder konkrete Klasse)
            $this->container->singleton($returnType, $factory);

        } catch (\Throwable $e) {
            // Safe Logger resolution - use if available
            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;

            $logger?->error(
                "Failed to register lazy service for return type: {$returnType}",
                LogContext::withExceptionAndData($e, [
                    'return_type' => $returnType,
                    'class' => $className,
                    'method' => $methodName,
                ])
            );

            // Service registration failed - continue to prevent breaking the entire application
        }
    }
}
