<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Factory;

use App\Framework\Cache\Cache;
use App\Framework\CommandBus\CommandHandlerMapper;
use App\Framework\Console\ConsoleCommandMapper;
use App\Framework\Context\ExecutionContext;
use App\Framework\Core\AttributeMapper;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\Events\EventHandlerMapper;
use App\Framework\Core\PathProvider;
use App\Framework\Core\RouteMapper;
use App\Framework\Database\Migration\Migration;
use App\Framework\DateTime\Clock;
use App\Framework\DI\Container;
use App\Framework\DI\DefaultContainer;
use App\Framework\DI\InitializerMapper;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Http\HttpMiddleware;
use App\Framework\Logging\Logger;
use App\Framework\Mcp\McpResourceMapper;
use App\Framework\Mcp\McpToolMapper;
use App\Framework\Performance\MemoryMonitor;
use App\Framework\QueryBus\QueryHandlerMapper;
use App\Framework\Reflection\CachedReflectionProvider;
use App\Framework\Reflection\ReflectionProvider;
use App\Framework\Template\Processing\DomProcessor;
use App\Framework\Template\Processing\StringProcessor;

/**
 * Factory for creating properly configured DiscoveryService instances
 *
 * Centralizes the complex dependency creation and configuration logic
 * that was previously scattered in constructors and bootstrappers.
 */
final readonly class DiscoveryServiceFactory
{
    public function __construct(
        private Container $container,
        private PathProvider $pathProvider,
        private Cache $cache,
        private Clock $clock
    ) {
    }

    /**
     * Create a fully configured DiscoveryService
     */
    public function create(DiscoveryConfiguration $config): UnifiedDiscoveryService
    {
        // Validate configuration
        $config->validate();

        // Resolve or create required dependencies
        $reflectionProvider = $this->resolveReflectionProvider();
        $fileSystemService = $this->resolveFileSystemService();

        // Resolve optional dependencies based on configuration
        $logger = $config->enablePerformanceTracking
            ? $this->resolveLogger()
            : null;

        $eventDispatcher = $config->enableEventDispatcher
            ? $this->resolveEventDispatcher()
            : null;

        $memoryMonitor = $config->enableMemoryMonitoring
            ? $this->resolveMemoryMonitor()
            : null;

        // Merge configured mappers with defaults
        $attributeMappers = $this->buildAttributeMappers($config->attributeMappers);
        $targetInterfaces = $this->buildTargetInterfaces($config->targetInterfaces);

        // Try to get ExecutionContext from container, or detect it
        $executionContext = null;
        if ($this->container->has(ExecutionContext::class)) {
            $executionContext = $this->container->get(ExecutionContext::class);
        } else {
            $executionContext = ExecutionContext::detect();
        }

        return new UnifiedDiscoveryService(
            pathProvider: $this->pathProvider,
            cache: $this->cache,
            clock: $this->clock,
            reflectionProvider: $reflectionProvider,
            configuration: $config,
            attributeMappers: $attributeMappers,
            targetInterfaces: $targetInterfaces,
            logger: $logger,
            eventDispatcher: $eventDispatcher,
            memoryMonitor: $memoryMonitor,
            fileSystemService: $fileSystemService,
            executionContext: $executionContext
        );
    }

    /**
     * Create DiscoveryService for development environment
     */
    public function createForDevelopment(array $paths = []): UnifiedDiscoveryService
    {
        $config = DiscoveryConfiguration::development();
        $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
        $config = $config->withPaths($scanPaths);

        return $this->create($config);
    }

    /**
     * Create DiscoveryService for production environment
     */
    public function createForProduction(array $paths = []): UnifiedDiscoveryService
    {
        $config = DiscoveryConfiguration::production();
        $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
        $config = $config->withPaths($scanPaths);

        return $this->create($config);
    }

    /**
     * Create DiscoveryService for testing environment
     */
    public function createForTesting(array $paths = []): UnifiedDiscoveryService
    {
        $config = DiscoveryConfiguration::testing();
        $scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
        $config = $config->withPaths($scanPaths);

        return $this->create($config);
    }

    /**
     * Create DiscoveryService with custom configuration
     */
    public function createWithCustomConfig(
        array $paths = [],
        bool $useCache = true,
        array $attributeMappers = [],
        array $targetInterfaces = []
    ): UnifiedDiscoveryService {
        $config = new DiscoveryConfiguration(
            paths: $paths,
            attributeMappers: $attributeMappers,
            targetInterfaces: $targetInterfaces,
            useCache: $useCache
        );

        return $this->create($config);
    }

    /**
     * Resolve ReflectionProvider from container or create default
     */
    private function resolveReflectionProvider(): ReflectionProvider
    {
        if ($this->container->has(ReflectionProvider::class)) {
            return $this->container->get(ReflectionProvider::class);
        }

        return new CachedReflectionProvider();
    }

    /**
     * Resolve FileSystemService from container or create default
     */
    private function resolveFileSystemService(): FileSystemService
    {
        if ($this->container->has(FileSystemService::class)) {
            return $this->container->get(FileSystemService::class);
        }

        return new FileSystemService();
    }

    /**
     * Resolve Logger from container if available
     */
    private function resolveLogger(): ?Logger
    {
        return $this->container->has(Logger::class)
            ? $this->container->get(Logger::class)
            : null;
    }

    /**
     * Resolve EventDispatcher from container if available
     */
    private function resolveEventDispatcher(): ?EventDispatcher
    {
        return $this->container->has(EventDispatcher::class)
            ? $this->container->get(EventDispatcher::class)
            : null;
    }

    /**
     * Resolve MemoryMonitor from container if available
     */
    private function resolveMemoryMonitor(): ?MemoryMonitor
    {
        return $this->container->has(MemoryMonitor::class)
            ? $this->container->get(MemoryMonitor::class)
            : null;
    }

    /**
     * Build attribute mappers array with defaults and user-provided mappers
     */
    private function buildAttributeMappers(array $customMappers = []): array
    {
        $defaultMappers = [
            new RouteMapper(),
            new EventHandlerMapper(),
            new \App\Framework\EventBus\EventHandlerMapper(),
            new QueryHandlerMapper(),
            new CommandHandlerMapper(),
            new InitializerMapper(),
            new McpToolMapper(),
            new McpResourceMapper(),
            new ConsoleCommandMapper(),
        ];

        // Merge custom mappers with defaults, allowing override by class name
        $mappers = [];
        $mapperClasses = [];

        // Add default mappers first
        foreach ($defaultMappers as $mapper) {
            $className = get_class($mapper);
            $mappers[] = $mapper;
            $mapperClasses[] = $className;
        }

        // Add custom mappers, replacing defaults if same class
        foreach ($customMappers as $mapper) {
            $className = get_class($mapper);
            $existingIndex = array_search($className, $mapperClasses, true);

            if ($existingIndex !== false) {
                // Replace existing mapper
                $mappers[$existingIndex] = $mapper;
            } else {
                // Add new mapper
                $mappers[] = $mapper;
                $mapperClasses[] = $className;
            }
        }

        return $mappers;
    }

    /**
     * Build target interfaces array with defaults and user-provided interfaces
     */
    private function buildTargetInterfaces(array $customInterfaces = []): array
    {
        $defaultInterfaces = [
            AttributeMapper::class,
            HttpMiddleware::class,
            DomProcessor::class,
            StringProcessor::class,
            Migration::class,
        ];

        // Merge and deduplicate
        return array_values(array_unique(array_merge($defaultInterfaces, $customInterfaces)));
    }

    /**
     * Create a lightweight factory for specific use cases
     */
    public static function lightweight(
        PathProvider $pathProvider,
        Cache $cache,
        Clock $clock
    ): self {
        // Create a minimal container for basic dependencies
        $container = new DefaultContainer();

        return new self($container, $pathProvider, $cache, $clock);
    }

    /**
     * Validate that all required services are available
     * @return string[]
     */
    public function validateDependencies(DiscoveryConfiguration $config): array
    {
        $issues = [];

        // Check required dependencies
        if (! $this->container->has(PathProvider::class) && ! isset($this->pathProvider)) {
            $issues[] = 'PathProvider is required but not available';
        }

        if (! $this->container->has(Cache::class) && ! isset($this->cache)) {
            $issues[] = 'Cache is required but not available';
        }

        if (! $this->container->has(Clock::class) && ! isset($this->clock)) {
            $issues[] = 'Clock is required but not available';
        }

        // Check optional dependencies if enabled
        if ($config->enableEventDispatcher && ! $this->container->has(EventDispatcher::class)) {
            $issues[] = 'EventDispatcher is enabled but not available in container';
        }

        if ($config->enableMemoryMonitoring && ! $this->container->has(MemoryMonitor::class)) {
            $issues[] = 'MemoryMonitor is enabled but not available in container';
        }

        if ($config->enablePerformanceTracking && ! $this->container->has(Logger::class)) {
            $issues[] = 'Logger is needed for performance tracking but not available in container';
        }

        return $issues;
    }

    /**
     * Get default scan paths for discovery
     * @return string[]
     */
    private function getDefaultScanPaths(): array
    {
        $basePath = $this->pathProvider->getBasePath();


        return [
            $this->pathProvider->getSourcePath(),
            #$basePath . '/src',
            #$basePath . '/app', // Support for app/ directory structure
        ];
    }
}
