<?php
declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Attributes\Route as RouteAttribute;
use App\Framework\Cache\Cache;
use App\Framework\Discovery\FileVisitor;

/**
 * Visitor zum Auffinden von Routen
 */
final class RouteDiscoveryVisitor implements FileVisitor
{
    private array $routes = [];
    private array $routesByPath = [];
    private array $routesByController = [];
    private bool $routesSorted = false;

    public function onScanStart(): void
    {
        $this->routes = [];
        $this->routesByPath = [];
        $this->routesByController = [];
        $this->routesSorted = false;
    }

    public function onIncrementalScanStart(): void
    {
        // Bei inkrementellem Scan behalten wir bestehende Routen
        // markieren sie aber als unsortiert
        $this->routesSorted = false;
    }

    public function onIncrementalScanComplete(): void
    {
        // Nach inkrementellem Scan sortieren
        $this->sortRoutes();
    }

    public function visitClass(string $className, string $filePath): void
    {
        if (!class_exists($className)) {
            return;
        }

        try {
            $reflection = new \ReflectionClass($className);

            // Prüfe, ob die Klasse überhaupt Methoden mit Route-Attributen hat
            $hasRouteMethods = false;
            foreach ($reflection->getMethods() as $method) {
                if (!empty($method->getAttributes(RouteAttribute::class))) {
                    $hasRouteMethods = true;
                    break;
                }
            }

            if (!$hasRouteMethods) {
                return;
            }

            // Nur Methoden mit Route-Attribut verarbeiten
            foreach ($reflection->getMethods() as $method) {
                $routeAttributes = $method->getAttributes(RouteAttribute::class);

                foreach ($routeAttributes as $routeAttr) {
                    /** @var RouteAttribute $route */
                    $route = $routeAttr->newInstance();

                    $routeData = [
                        'path' => $route->path,
                        'method' => $route->method,
                        'controller' => $className,
                        'action' => $method->getName(),
                        'middleware' => $route->middleware ?? [],
                        'priority' => $route->priority ?? 0
                    ];

                    $this->routes[] = $routeData;

                    // Erstelle Indizes für schnelleren Zugriff
                    $path = strtolower($route->path);
                    $httpMethod = strtoupper($route->method->value);
                    $key = "{$httpMethod}:{$path}";

                    if (!isset($this->routesByPath[$key])) {
                        $this->routesByPath[$key] = [];
                    }
                    $this->routesByPath[$key][] = $routeData;

                    if (!isset($this->routesByController[$className])) {
                        $this->routesByController[$className] = [];
                    }
                    $this->routesByController[$className][] = $routeData;
                }
            }
        } catch (\Throwable $e) {
            error_log("Fehler beim Verarbeiten der Routen für {$className}: " . $e->getMessage());
        }
    }

    public function onScanComplete(): void
    {
        // Sortiere Routen nach Priorität
        $this->sortRoutes();
    }

    /**
     * Sortiert die Routen nach Priorität (absteigend)
     */
    private function sortRoutes(): void
    {
        if ($this->routesSorted) {
            return;
        }

        // Sortiere Hauptrouten-Array
        usort($this->routes, function($a, $b) {
            return $b['priority'] <=> $a['priority'];
        });

        // Sortiere Indizes
        foreach ($this->routesByPath as $key => $routes) {
            usort($this->routesByPath[$key], function($a, $b) {
                return $b['priority'] <=> $a['priority'];
            });
        }

        foreach ($this->routesByController as $className => $routes) {
            usort($this->routesByController[$className], function($a, $b) {
                return $b['priority'] <=> $a['priority'];
            });
        }

        $this->routesSorted = true;
    }

    public function loadFromCache(Cache $cache): void
    {
        $cacheItem = $cache->get($this->getCacheKey());
        if ($cacheItem->isHit) {
            if (is_array($cacheItem->value) && isset($cacheItem->value['routes'], $cacheItem->value['byPath'], $cacheItem->value['byController'])) {
                $this->routes = $cacheItem->value['routes'];
                $this->routesByPath = $cacheItem->value['byPath'];
                $this->routesByController = $cacheItem->value['byController'];
                $this->routesSorted = true;
            }
        }
    }

    public function getCacheKey(): string
    {
        return 'routes';
    }

    public function getCacheableData(): mixed
    {
        // Stelle sicher, dass die Routen sortiert sind, bevor wir sie cachen
        if (!$this->routesSorted) {
            $this->sortRoutes();
        }

        return [
            'routes' => $this->routes,
            'byPath' => $this->routesByPath,
            'byController' => $this->routesByController
        ];
    }

    /**
     * Gibt alle gefundenen Routen zurück
     */
    public function getRoutes(): array
    {
        if (!$this->routesSorted) {
            $this->sortRoutes();
        }
        return $this->routes;
    }

    /**
     * Findet eine Route für einen bestimmten Pfad und HTTP-Methode
     */
    public function findRoute(string $path, string $httpMethod): ?array
    {
        $path = strtolower(rtrim($path, '/'));
        $httpMethod = strtoupper($httpMethod);
        $key = "{$httpMethod}:{$path}";

        // Direkte Übereinstimmung
        if (isset($this->routesByPath[$key]) && !empty($this->routesByPath[$key])) {
            return $this->routesByPath[$key][0]; // Höchste Priorität zuerst
        }

        // Prüfe auf parametrisierte Routen, wenn keine direkte Übereinstimmung
        foreach ($this->routesByPath as $routeKey => $routes) {
            list($routeMethod, $routePath) = explode(':', $routeKey, 2);

            if ($routeMethod !== $httpMethod) {
                continue;
            }

            // Konvertiere Route-Pfad in ein reguläres Ausdrucksmuster
            $pattern = $this->routePathToRegex($routePath);
            if (preg_match($pattern, $path)) {
                return $routes[0]; // Höchste Priorität zuerst
            }
        }

        return null;
    }

    /**
     * Konvertiert einen Route-Pfad in ein reguläres Ausdrucksmuster
     */
    private function routePathToRegex(string $routePath): string
    {
        // Ersetze Platzhalter wie {id} durch Regex-Gruppen
        $pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $routePath);
        return '/^' . str_replace('/', '\/', $pattern) . '$/';
    }

    /**
     * Gibt alle Routen für einen bestimmten Controller zurück
     */
    public function getRoutesForController(string $controllerClass): array
    {
        return $this->routesByController[$controllerClass] ?? [];
    }
}
