<?php

declare(strict_types=1);

namespace App\Framework\Http;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\DI\Container;
use App\Framework\Logging\Logger;
use App\Framework\Reflection\ReflectionProvider;

/**
 * Resolves middleware dependencies by analyzing constructor parameters
 * and automatically sorting middlewares in the correct execution order.
 *
 * Enhanced with robustness features:
 * - ClassName value objects for type safety
 * - Fallback strategies for critical middlewares
 * - Comprehensive error handling and logging
 * - Protection for essential middlewares
 */
final readonly class MiddlewareDependencyResolver
{
    /** @var array<string> Critical middlewares that must never be filtered out */
    private const array CRITICAL_MIDDLEWARES = [
        #'App\\Framework\\Http\\Middlewares\\DefaultResponseMiddleware',
        'App\\Framework\\Http\\Middlewares\\ExceptionHandlingMiddleware',
        'App\\Framework\\Http\\Middlewares\\RequestIdMiddleware',
        'App\\Framework\\Http\\Middlewares\\RoutingMiddleware',
    ];

    public function __construct(
        private ReflectionProvider $reflectionProvider,
        private Container $container,
        private Logger $logger
    ) {
    }

    /**
     * Resolve middleware dependencies and return them in correct execution order
     *
     * @param array<string> $middlewareClasses
     * @throws MiddlewareDependencyException
     */
    public function resolve(array $middlewareClasses): ResolvedMiddlewareStack
    {
        try {
            $classNames = $this->normalizeClassNames($middlewareClasses);

            $this->logger->debug("Starting resolution for " . count($classNames) . " middlewares");

            $dependencyGraph = $this->buildDependencyGraph($classNames);
            $simplifiedGraph = $this->extractDependenciesForSort($dependencyGraph);
            $sortedClasses = $this->topologicalSort($simplifiedGraph);
            $availableMiddlewares = $this->filterAvailableWithFallbacks($sortedClasses);

            $this->validateCriticalMiddlewares($availableMiddlewares);

            $this->logger->info("Resolution completed with " . count($availableMiddlewares) . " middlewares");

            return new ResolvedMiddlewareStack($availableMiddlewares, $dependencyGraph);
        } catch (MiddlewareDependencyException $e) {
            throw $e;
        } catch (\Throwable $e) {
            $this->logger->critical("Critical error during resolution: " . $e->getMessage());

            throw MiddlewareDependencyException::cannotInstantiate(
                ClassName::create('Unknown'),
                "Dependency resolution failed: " . $e->getMessage()
            );
        }
    }

    /**
     * Normalize class names to ClassName value objects
     *
     * @param array<string> $middlewareClasses
     * @return array<ClassName>
     */
    private function normalizeClassNames(array $middlewareClasses): array
    {
        $classNames = [];

        foreach ($middlewareClasses as $middlewareClass) {
            try {
                $className = ClassName::create($middlewareClass);
                if ($className->exists()) {
                    $classNames[] = $className;
                } else {
                    $this->logger->warning("Class not found: {$className->getShortName()}");
                }
            } catch (\InvalidArgumentException $e) {
                $this->logger->warning("Invalid class name: {$middlewareClass} - " . $e->getMessage());
            }
        }

        return $classNames;
    }

    /**
     * Build dependency graph by analyzing constructor parameters
     *
     * @param array<ClassName> $middlewareClasses
     * @return array<string, array{dependencies: array<string>, services: array<string>, provides: array<string>, optional: array<string>, is_critical: bool}>
     */
    private function buildDependencyGraph(array $middlewareClasses): array
    {
        $graph = [];

        foreach ($middlewareClasses as $className) {
            $classNameString = $className->getFullyQualified();

            try {
                $graph[$classNameString] = [
                    'dependencies' => $this->getMiddlewareDependencies($className),
                    'services' => $this->getServiceDependencies($className),
                    'provides' => $this->getProvidedServices($className),
                    'optional' => $this->getOptionalDependencies($className),
                    'is_critical' => $this->isCriticalMiddleware($className),
                ];
            } catch (\Throwable $e) {
                $this->logger->error("Error analyzing {$className->getShortName()}: " . $e->getMessage());

                // Add minimal entry for critical middlewares even if analysis fails
                if ($this->isCriticalMiddleware($className)) {
                    $graph[$classNameString] = [
                        'dependencies' => [],
                        'services' => [],
                        'provides' => [],
                        'optional' => [],
                        'is_critical' => true,
                    ];
                }
            }
        }

        return $graph;
    }

    /**
     * Extract dependencies for topological sorting
     * @param array<string, array{dependencies: array<string>, services: array<string>, provides: array<string>, optional: array<string>, is_critical: bool}> $dependencyGraph
     * @return array<string, array<string>>
     */
    private function extractDependenciesForSort(array $dependencyGraph): array
    {
        $simplifiedGraph = [];

        foreach ($dependencyGraph as $className => $data) {
            $simplifiedGraph[$className] = $data['dependencies'];
        }

        return $simplifiedGraph;
    }

    /**
     * Check if middleware is critical and must never be filtered out
     */
    private function isCriticalMiddleware(ClassName $className): bool
    {
        return in_array($className->getFullyQualified(), self::CRITICAL_MIDDLEWARES, true);
    }

    /**
     * Get middleware dependencies from constructor parameters
     * @return array<string>
     */
    private function getMiddlewareDependencies(ClassName $middlewareClass): array
    {
        try {
            if (! $middlewareClass->exists()) {
                return [];
            }

            $dependencies = [];
            $parameterCollection = $this->reflectionProvider->getMethodParameters(
                $middlewareClass,
                '__construct'
            );

            foreach ($parameterCollection as $parameter) {
                if ($parameter->isBuiltin()) {
                    continue;
                }

                $typeName = $parameter->getTypeName();
                if (! $typeName) {
                    continue;
                }

                $dependencyClass = ClassName::create($typeName);

                // Check if this parameter is another middleware
                if ($this->isMiddleware($dependencyClass)) {
                    $dependencies[] = $dependencyClass->getFullyQualified();
                }
            }

            return $dependencies;
        } catch (\Throwable $e) {
            $this->logger->error("Error analyzing {$middlewareClass->getShortName()}: " . $e->getMessage());

            return [];
        }
    }

    /**
     * Get non-middleware service dependencies
     * @return array<string>
     */
    private function getServiceDependencies(ClassName $middlewareClass): array
    {
        try {
            if (! $middlewareClass->exists()) {
                return [];
            }

            $services = [];
            $parameterCollection = $this->reflectionProvider->getMethodParameters(
                $middlewareClass,
                '__construct'
            );

            foreach ($parameterCollection as $parameter) {
                if ($parameter->isBuiltin()) {
                    continue;
                }

                $typeName = $parameter->getTypeName();
                if (! $typeName) {
                    continue;
                }

                $serviceClass = ClassName::create($typeName);

                // Include non-middleware services
                if (! $this->isMiddleware($serviceClass)) {
                    $services[] = $serviceClass->getFullyQualified();
                }
            }

            return $services;
        } catch (\Throwable $e) {
            $this->logger->error("Error analyzing services for {$middlewareClass->getShortName()}: " . $e->getMessage());

            return [];
        }
    }

    /**
     * Check what services this middleware provides (for future use)
     */
    /**
     * @return array<string>
     */
    private function getProvidedServices(ClassName $middlewareClass): array
    {
        // This could be extended with attributes like #[Provides(['session', 'auth'])]
        $provides = [];
        $className = $middlewareClass->getShortName();

        // Basic heuristics based on class name
        if (str_contains($className, 'Session')) {
            $provides[] = 'session';
        }
        if (str_contains($className, 'Auth')) {
            $provides[] = 'authentication';
        }
        if (str_contains($className, 'Csrf')) {
            $provides[] = 'csrf_protection';
        }
        if (str_contains($className, 'Response')) {
            $provides[] = 'response_generation';
        }
        if (str_contains($className, 'Routing')) {
            $provides[] = 'routing';
        }

        return $provides;
    }

    /**
     * Get optional dependencies (non-required)
     */
    /**
     * @return array<string>
     */
    private function getOptionalDependencies(ClassName $middlewareClass): array
    {
        try {
            if (! $middlewareClass->exists()) {
                return [];
            }

            $optional = [];
            $parameterCollection = $this->reflectionProvider->getMethodParameters(
                $middlewareClass,
                '__construct'
            );

            foreach ($parameterCollection as $parameter) {
                if ($parameter->isOptional()) {
                    if (! $parameter->isBuiltin()) {
                        $typeName = $parameter->getTypeName();
                        if ($typeName) {
                            $optional[] = $typeName;
                        }
                    }
                }
            }

            return $optional;
        } catch (\Throwable $e) {
            $this->logger->error("Error analyzing optional dependencies for {$middlewareClass->getShortName()}: " . $e->getMessage());

            return [];
        }
    }

    /**
     * Check if a class is a middleware
     */
    private function isMiddleware(ClassName $className): bool
    {
        try {
            if (! $className->exists()) {
                return false;
            }

            return $this->reflectionProvider->implementsInterface(
                $className,
                HttpMiddleware::class
            );
        } catch (\Throwable $e) {
            $this->logger->error("Error checking middleware interface for {$className->getShortName()}: " . $e->getMessage());

            return false;
        }
    }

    /**
     * Topological sort to determine execution order
     */
    /**
     * @param array<string, array<string>> $graph
     * @return array<string>
     */
    private function topologicalSort(array $graph): array
    {
        $sorted = [];
        $visited = [];
        $visiting = [];

        foreach (array_keys($graph) as $node) {
            if (! isset($visited[$node])) {
                $result = $this->topologicalSortVisit($node, $graph, $visited, $visiting, $sorted);
                $visited = $result['visited'];
                $visiting = $result['visiting'];
                $sorted = $result['sorted'];
            }
        }

        return array_reverse($sorted);
    }

    /**
     * Recursive topological sort visit (returns result instead of using references)
     * @param array<string, array<string>> $graph
     * @param array<string, bool> $visited
     * @param array<string, bool> $visiting
     * @param array<string> $sorted
     * @return array{visited: array<string, bool>, visiting: array<string, bool>, sorted: array<string>}
     */
    private function topologicalSortVisit(string $node, array $graph, array $visited, array $visiting, array $sorted): array
    {
        if (isset($visiting[$node])) {
            // Circular dependency detected
            $this->logger->warning("Circular dependency detected involving {$node}");

            return ['visited' => $visited, 'visiting' => $visiting, 'sorted' => $sorted];
        }

        if (isset($visited[$node])) {
            return ['visited' => $visited, 'visiting' => $visiting, 'sorted' => $sorted];
        }

        $visiting[$node] = true;

        // Visit dependencies first
        foreach ($graph[$node] as $dependency) {
            if (isset($graph[$dependency])) {
                $result = $this->topologicalSortVisit($dependency, $graph, $visited, $visiting, $sorted);
                $visited = $result['visited'];
                $visiting = $result['visiting'];
                $sorted = $result['sorted'];
            }
        }

        unset($visiting[$node]);
        $visited[$node] = true;
        $sorted[] = $node;

        return ['visited' => $visited, 'visiting' => $visiting, 'sorted' => $sorted];
    }

    /**
     * Filter out middlewares that can't be instantiated with fallback strategies
     * @param array<string> $middlewareClasses
     * @return array<string>
     */
    private function filterAvailableWithFallbacks(array $middlewareClasses): array
    {
        $available = [];
        $filtered = [];

        foreach ($middlewareClasses as $middlewareClass) {
            $className = ClassName::create($middlewareClass);

            if ($this->canInstantiate($className)) {
                $available[] = $middlewareClass;
            } else {
                $filtered[] = $className;

                // Critical middlewares get special treatment
                if ($this->isCriticalMiddleware($className)) {
                    // debug: CRITICAL - Cannot instantiate {$className->getShortName()}, applying fallback strategy

                    // Try fallback strategies for critical middlewares
                    if ($this->attemptFallbackInstantiation($className)) {
                        $available[] = $middlewareClass;
                        // debug: SUCCESS - Fallback strategy worked for {$className->getShortName()}
                    } else {
                        // debug: FAILURE - All fallback strategies failed for {$className->getShortName()}
                    }
                } else {
                    // debug: Cannot instantiate {$className->getShortName()}, skipping (not critical)
                }
            }
        }

        if (! empty($filtered)) {
            $filteredNames = array_map(fn (ClassName $c) => $c->getShortName(), $filtered);
            $this->logger->warning("Filtered out " . count($filtered) . " middlewares: " . implode(', ', $filteredNames));
        }

        return $available;
    }

    /**
     * Attempt fallback instantiation strategies for critical middlewares
     */
    private function attemptFallbackInstantiation(ClassName $className): bool
    {
        try {
            // Strategy 1: Try basic instantiation without dependency checking
            if ($this->reflectionProvider->isInstantiable($className)) {
                $this->logger->debug("Fallback strategy 1 (basic instantiation) succeeded for {$className->getShortName()}");

                return true;
            }

            // Strategy 2: Check if we can provide minimal dependencies
            $parameterCollection = $this->reflectionProvider->getMethodParameters(
                $className,
                '__construct'
            );

            $canProvideAll = true;
            foreach ($parameterCollection as $parameter) {
                if ($parameter->isBuiltin()) {
                    continue;
                }

                if (! $parameter->isOptional()) {
                    $typeName = $parameter->getTypeName();
                    if (! $typeName || ! $this->container->has($typeName)) {
                        $canProvideAll = false;

                        break;
                    }
                }
            }

            if ($canProvideAll) {
                $this->logger->debug("Fallback strategy 2 (dependency provision) succeeded for {$className->getShortName()}");

                return true;
            }

            // Strategy 3: For absolutely critical middlewares, allow them anyway
            if ($this->isCriticalMiddleware($className)) {
                $this->logger->warning("Fallback strategy 3 (critical override) applied for {$className->getShortName()}");

                return true;
            }

            return false;
        } catch (\Throwable $e) {
            $this->logger->error("Fallback strategies failed for {$className->getShortName()}: " . $e->getMessage());

            return false;
        }
    }

    /**
     * Check if a middleware can be instantiated by trying to resolve it through the container
     */
    private function canInstantiate(ClassName $middlewareClass): bool
    {
        try {
            // Check if class exists
            if (! $middlewareClass->exists()) {
                $this->logger->warning("Class does not exist: {$middlewareClass->getShortName()}");

                return false;
            }

            // Check if class is instantiable
            if (! $this->reflectionProvider->isInstantiable($middlewareClass)) {
                $this->logger->warning("Class is not instantiable: {$middlewareClass->getShortName()}");

                return false;
            }

            // Try to actually resolve the middleware through the container
            // This is the most reliable way to check if all dependencies are available
            try {
                /** @var class-string<object> $className */
                $className = $middlewareClass->getFullyQualified();
                $instance = $this->container->get($className);
                $this->logger->debug("Successfully resolved {$middlewareClass->getShortName()} through container");

                return true;
            } catch (\Throwable $containerException) {
                $this->logger->warning("Cannot resolve {$middlewareClass->getShortName()} through container: " . $containerException->getMessage());

                // Fallback to the old dependency checking method for more detailed info
                return $this->checkDependenciesManually($middlewareClass);
            }

        } catch (\Throwable $e) {
            $this->logger->error("Error checking instantiation for {$middlewareClass->getShortName()}: " . $e->getMessage());

            return false;
        }
    }

    /**
     * Fallback method to manually check dependencies when container resolution fails
     */
    private function checkDependenciesManually(ClassName $middlewareClass): bool
    {
        try {
            $parameterCollection = $this->reflectionProvider->getMethodParameters(
                $middlewareClass,
                '__construct'
            );

            $missingDependencies = [];

            foreach ($parameterCollection as $parameter) {
                // Skip built-in types
                if ($parameter->isBuiltin()) {
                    continue;
                }

                $typeName = $parameter->getTypeName();
                if (! $typeName) {
                    continue;
                }

                // Check if required dependency is available in container
                if (! $parameter->isOptional()) {
                    if (! $this->container->has($typeName)) {
                        $missingDependencies[] = ClassName::create($typeName);
                    }
                }
            }

            if (! empty($missingDependencies)) {
                $depNames = array_map(fn (ClassName $dep) => $dep->getShortName(), $missingDependencies);
                $this->logger->debug("Manual dependency check - Missing dependencies for {$middlewareClass->getShortName()}: " . implode(', ', $depNames));

                return false;
            }

            return true;
        } catch (\Throwable $e) {
            $this->logger->error("Error in manual dependency check for {$middlewareClass->getShortName()}: " . $e->getMessage());

            return false;
        }
    }

    /**
     * Validate that all critical middlewares are present
     *
     * @throws MiddlewareDependencyException
     */
    /**
     * @param array<string> $availableMiddlewares
     */
    private function validateCriticalMiddlewares(array $availableMiddlewares): void
    {
        $missingCritical = [];

        foreach (self::CRITICAL_MIDDLEWARES as $criticalMiddleware) {
            if (! in_array($criticalMiddleware, $availableMiddlewares, true)) {
                $missingCritical[] = ClassName::create($criticalMiddleware);
            }
        }

        if (! empty($missingCritical)) {
            $missingNames = array_map(fn (ClassName $c) => $c->getShortName(), $missingCritical);
            $this->logger->critical("CRITICAL - Missing essential middlewares: " . implode(', ', $missingNames));

            throw MiddlewareDependencyException::missingDependencies(
                ClassName::create('CriticalMiddlewares'),
                $missingCritical
            );
        }
    }

    /**
     * Get dependency information for debugging
     *
     * @param array<string> $middlewareClasses
     * @return array<string, mixed>
     */
    public function getDependencyInfo(array $middlewareClasses): array
    {
        $info = [];
        $classNames = $this->normalizeClassNames($middlewareClasses);

        foreach ($classNames as $middlewareClass) {
            try {
                $info[$middlewareClass->getFullyQualified()] = [
                    'short_name' => $middlewareClass->getShortName(),
                    'namespace' => $middlewareClass->getNamespace(),
                    'exists' => $middlewareClass->exists(),
                    'is_instantiable' => $this->reflectionProvider->isInstantiable($middlewareClass),
                    'is_critical' => $this->isCriticalMiddleware($middlewareClass),
                    'middleware_dependencies' => $this->getMiddlewareDependencies($middlewareClass),
                    'service_dependencies' => $this->getServiceDependencies($middlewareClass),
                    'provides' => $this->getProvidedServices($middlewareClass),
                    'optional' => $this->getOptionalDependencies($middlewareClass),
                    'can_instantiate' => $this->canInstantiate($middlewareClass),
                ];
            } catch (\Throwable $e) {
                $info[$middlewareClass->getFullyQualified()] = [
                    'error' => $e->getMessage(),
                    'can_instantiate' => false,
                ];
            }
        }

        return $info;
    }
}
