<?php

declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Discovery\FileVisitor;
use App\Framework\Discovery\ReflectionAwareVisitor;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Reflection\WrappedReflectionClass;

/**
 * Visitor zum Verarbeiten von Attributen in Klassen
 */
final class AttributeMappingVisitor implements FileVisitor, ReflectionAwareVisitor
{
    private array $attributeMappers = [];

    private array $mappedAttributes = [];

    private array $mapperByClass = [];

    private array $processingStats = [];

    /**
     * @param array $attributeMappers Array von AttributeMapper-Instanzen
     */
    public function __construct(array $attributeMappers = [])
    {
        $this->attributeMappers = $attributeMappers;
        $this->buildMapperIndex();
    }

    /**
     * Erstellt einen Index der Mapper nach Attributklasse für schnelleren Zugriff
     */
    private function buildMapperIndex(): void
    {
        $this->mapperByClass = [];
        foreach ($this->attributeMappers as $mapper) {
            $attributeClass = $mapper->getAttributeClass();
            $this->mapperByClass[$attributeClass] = $mapper;
        }
    }

    public function onScanStart(): void
    {
        $this->mappedAttributes = [];
        $this->processingStats = [
            'total_classes' => 0,
            'classes_with_attributes' => 0,
            'total_attributes' => 0,
            'processed_attributes' => 0,
            'skipped_reflectors' => 0,
        ];
    }

    public function onIncrementalScanStart(): void
    {
        // Bei inkrementellem Scan behalten wir die vorhandenen Mappings bei
        $this->processingStats = [
            'total_classes' => 0,
            'classes_with_attributes' => 0,
            'total_attributes' => 0,
            'processed_attributes' => 0,
            'skipped_reflectors' => 0,
        ];
    }

    public function onIncrementalScanComplete(): void
    {
        // Logging der Verarbeitungsstatistiken für inkrementellen Scan
        // Processing stats removed for production
    }

    public function visitClass(ClassName $className, FilePath $filePath): void
    {
        // This method is kept for backward compatibility but should not be used
        // when ReflectionProvider is available. The UnifiedDiscoveryService will
        // prefer visitClassWithReflection() when possible.
    }

    public function visitClassWithReflection(ClassName $className, FilePath $filePath, WrappedReflectionClass $reflection): void
    {
        $this->processingStats['total_classes']++;
        $hasAttributes = false;

        try {
            // Schnelle Vorprüfung: Hat die Klasse überhaupt relevante Attribute?
            $relevantAttributes = $this->hasRelevantAttributesWrapped($reflection);
            if (! $relevantAttributes) {
                return;
            }

            $hasAttributes = true;
            $this->processingStats['classes_with_attributes']++;

            // Verarbeite alle Attribute auf Klassenebene
            $this->processAttributesWrapped($reflection->getAttributes(), $reflection);

            // Verarbeite alle Methoden
            $methods = $reflection->getMethods();
            foreach ($methods as $method) {
                // Schnelle Vorprüfung für jede Methode
                if ($this->hasRelevantAttributesWrappedMethod($method)) {
                    $this->processAttributesWrappedMethod($method->getAttributes(), $method);
                }
            }

            // Verarbeite alle Eigenschaften
            $properties = $reflection->getProperties();
            foreach ($properties as $property) {
                // Schnelle Vorprüfung für jede Eigenschaft
                if ($this->hasRelevantAttributesWrappedProperty($property)) {
                    $this->processAttributesWrappedProperty($property->getAttributes(), $property);
                }
            }
        } catch (\Throwable $e) {
            // Fehler beim Verarbeiten der Klasse protokollieren
            // Silent failure - skip this class and continue
        }
    }

    /**
     * Schnelle Vorprüfung, ob eine WrappedReflectionClass relevante Attribute hat
     */
    private function hasRelevantAttributesWrapped(WrappedReflectionClass $reflection): bool
    {
        $attributes = $reflection->getAttributes();
        $this->processingStats['total_attributes'] += $attributes->count();

        if ($attributes->isEmpty()) {
            return false;
        }

        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();
            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                // For wrapped reflection, we need to create native reflection for mapper compatibility
                try {
                    $nativeReflection = new \ReflectionClass($reflection->getName());
                    if ($mapper->canProcess($nativeReflection)) {
                        return true;
                    } else {
                        $this->processingStats['skipped_reflectors']++;
                    }
                } catch (\Throwable) {
                    $this->processingStats['skipped_reflectors']++;
                }
            }
        }

        return false;
    }

    /**
     * Schnelle Vorprüfung für WrappedReflectionMethod
     */
    private function hasRelevantAttributesWrappedMethod($method): bool
    {
        $attributes = $method->getAttributes();
        $this->processingStats['total_attributes'] += $attributes->count();

        if ($attributes->isEmpty()) {
            return false;
        }

        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();
            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                // For wrapped reflection, we need to create native reflection for mapper compatibility
                try {
                    $nativeClass = new \ReflectionClass($method->getDeclaringClass()->getName());
                    $nativeMethod = $nativeClass->getMethod($method->getName());
                    if ($mapper->canProcess($nativeMethod)) {
                        return true;
                    } else {
                        $this->processingStats['skipped_reflectors']++;
                    }
                } catch (\Throwable) {
                    $this->processingStats['skipped_reflectors']++;
                }
            }
        }

        return false;
    }

    /**
     * Schnelle Vorprüfung für WrappedReflectionProperty
     */
    private function hasRelevantAttributesWrappedProperty($property): bool
    {
        $attributes = $property->getAttributes();
        $this->processingStats['total_attributes'] += $attributes->count();

        if ($attributes->isEmpty()) {
            return false;
        }

        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();
            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                // For wrapped reflection, we need to create native reflection for mapper compatibility
                try {
                    $nativeClass = new \ReflectionClass($property->getDeclaringClass()->getName());
                    $nativeProperty = $nativeClass->getProperty($property->getName());
                    if ($mapper->canProcess($nativeProperty)) {
                        return true;
                    } else {
                        $this->processingStats['skipped_reflectors']++;
                    }
                } catch (\Throwable) {
                    $this->processingStats['skipped_reflectors']++;
                }
            }
        }

        return false;
    }

    /**
     * Verarbeitet Attribute einer WrappedReflectionClass
     */
    private function processAttributesWrapped($attributes, WrappedReflectionClass $reflection): void
    {
        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();

            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                try {
                    $nativeReflection = new \ReflectionClass($reflection->getName());
                    if ($mapper->canProcess($nativeReflection)) {
                        $attributeInstance = $attribute->newInstance();
                        $mapper->map($attributeInstance, $nativeReflection);
                        $this->processingStats['processed_attributes']++;

                        if (! isset($this->mappedAttributes[$attributeClass])) {
                            $this->mappedAttributes[$attributeClass] = [];
                        }

                        $reflectorKey = [
                            'type' => 'class',
                            'name' => $reflection->getName(),
                        ];

                        $this->mappedAttributes[$attributeClass][] = [
                            'attribute' => $attributeInstance,
                            'reflector' => $reflectorKey,
                            'metadata' => $mapper->getAttributeMetadata(),
                        ];
                    }
                } catch (\Throwable $e) {
                    // Silent failure - skip this attribute and continue
                }
            }
        }
    }

    /**
     * Verarbeitet Attribute einer WrappedReflectionMethod
     */
    private function processAttributesWrappedMethod($attributes, $method): void
    {
        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();

            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                try {
                    $nativeClass = new \ReflectionClass($method->getDeclaringClass()->getName());
                    $nativeMethod = $nativeClass->getMethod($method->getName());
                    if ($mapper->canProcess($nativeMethod)) {
                        $attributeInstance = $attribute->newInstance();
                        $mapper->map($attributeInstance, $nativeMethod);
                        $this->processingStats['processed_attributes']++;

                        if (! isset($this->mappedAttributes[$attributeClass])) {
                            $this->mappedAttributes[$attributeClass] = [];
                        }

                        $reflectorKey = [
                            'type' => 'method',
                            'class' => $method->getDeclaringClass()->getName(),
                            'name' => $method->getName(),
                        ];

                        $this->mappedAttributes[$attributeClass][] = [
                            'attribute' => $attributeInstance,
                            'reflector' => $reflectorKey,
                            'metadata' => $mapper->getAttributeMetadata(),
                        ];
                    }
                } catch (\Throwable $e) {
                    // Silent failure - skip this attribute and continue
                }
            }
        }
    }

    /**
     * Verarbeitet Attribute einer WrappedReflectionProperty
     */
    private function processAttributesWrappedProperty($attributes, $property): void
    {
        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();

            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                try {
                    $nativeClass = new \ReflectionClass($property->getDeclaringClass()->getName());
                    $nativeProperty = $nativeClass->getProperty($property->getName());
                    if ($mapper->canProcess($nativeProperty)) {
                        $attributeInstance = $attribute->newInstance();
                        $mapper->map($attributeInstance, $nativeProperty);
                        $this->processingStats['processed_attributes']++;

                        if (! isset($this->mappedAttributes[$attributeClass])) {
                            $this->mappedAttributes[$attributeClass] = [];
                        }

                        $reflectorKey = [
                            'type' => 'property',
                            'class' => $property->getDeclaringClass()->getName(),
                            'name' => $property->getName(),
                        ];

                        $this->mappedAttributes[$attributeClass][] = [
                            'attribute' => $attributeInstance,
                            'reflector' => $reflectorKey,
                            'metadata' => $mapper->getAttributeMetadata(),
                        ];
                    }
                } catch (\Throwable $e) {
                    // Silent failure - skip this attribute and continue
                }
            }
        }
    }

    /**
     * Schnelle Vorprüfung, ob ein Reflector relevante Attribute hat
     */
    private function hasRelevantAttributes(\Reflector $reflector): bool
    {
        if (method_exists($reflector, 'getAttributes')) {
            $attributes = $reflector->getAttributes();
            $this->processingStats['total_attributes'] += count($attributes);

            if (empty($attributes)) {
                return false;
            }

            foreach ($attributes as $attribute) {
                $attributeClass = $attribute->getName();
                if (isset($this->mapperByClass[$attributeClass])) {
                    $mapper = $this->mapperByClass[$attributeClass];
                    if ($mapper->canProcess($reflector)) {
                        return true;
                    } else {
                        $this->processingStats['skipped_reflectors']++;
                    }
                }
            }
        }

        return false;
    }

    private function processAttributes(array $attributes, \Reflector $reflector): void
    {
        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();

            // Direkter Zugriff auf den Mapper über den Index
            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                // Zusätzliche Prüfung, ob der Mapper diesen Reflector verarbeiten kann
                if ($mapper->canProcess($reflector)) {
                    try {
                        $attributeInstance = $attribute->newInstance();
                        $mapper->map($attributeInstance, $reflector);
                        $this->processingStats['processed_attributes']++;

                        // Speichere für Caching
                        if (! isset($this->mappedAttributes[$attributeClass])) {
                            $this->mappedAttributes[$attributeClass] = [];
                        }

                        // Serialisierbare Repräsentation des Reflectors
                        $reflectorKey = $this->serializeReflector($reflector);

                        $this->mappedAttributes[$attributeClass][] = [
                            'attribute' => $attributeInstance,
                            'reflector' => $reflectorKey,
                            'metadata' => $mapper->getAttributeMetadata(),
                        ];
                    } catch (\Throwable $e) {
                        // Silent failure - skip this attribute and continue
                    }
                }
            }
        }
    }

    /**
     * Erstellt eine serialisierbare Repräsentation eines Reflectors
     */
    private function serializeReflector(\Reflector $reflector): array
    {
        if ($reflector instanceof \ReflectionClass) {
            return [
                'type' => 'class',
                'name' => $reflector->getName(),
            ];
        } elseif ($reflector instanceof \ReflectionMethod) {
            return [
                'type' => 'method',
                'class' => $reflector->getDeclaringClass()->getName(),
                'name' => $reflector->getName(),
            ];
        } elseif ($reflector instanceof \ReflectionProperty) {
            return [
                'type' => 'property',
                'class' => $reflector->getDeclaringClass()->getName(),
                'name' => $reflector->getName(),
            ];
        }

        return [
            'type' => 'unknown',
            'name' => method_exists($reflector, 'getName') ? $reflector->getName() : 'unknown',
        ];
    }

    public function onScanComplete(): void
    {
        // Logging der Verarbeitungsstatistiken
        // Processing completed - stats removed for production
    }

    public function loadFromCache(Cache $cache): void
    {
        $cacheItem = $cache->get(CacheKey::fromString($this->getCacheKey()));
        if ($cacheItem->isHit) {
            $this->mappedAttributes = $cacheItem->value;
        }
    }

    public function getCacheKey(): CacheKey
    {
        return CacheKey::fromString('attribute_mappings');
    }

    public function getCacheableData(): mixed
    {
        return $this->mappedAttributes;
    }

    /**
     * Fügt einen weiteren AttributeMapper hinzu
     */
    public function addAttributeMapper(AttributeMapper $mapper): void
    {
        $this->attributeMappers[] = $mapper;

        // Aktualisiere den Mapper-Index
        $attributeClass = $mapper->getAttributeClass();
        $this->mapperByClass[$attributeClass] = $mapper;
    }

    /**
     * Gibt alle Attribute eines bestimmten Typs zurück
     */
    public function getAttributesOfType(string $attributeClass): array
    {
        return $this->mappedAttributes[$attributeClass] ?? [];
    }

    /**
     * Gibt alle gemappten Attribute zurück
     */
    public function getAllMappedAttributes(): array
    {
        return $this->mappedAttributes;
    }

    /**
     * Gibt die Verarbeitungsstatistiken zurück
     */
    public function getProcessingStats(): array
    {
        return $this->processingStats;
    }
}
