<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Storage;

use App\Framework\Async\FiberManager;
use App\Framework\Filesystem\Directory;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\PermissionChecker;
use App\Framework\Filesystem\Storage;
use App\Framework\Filesystem\ValueObjects\FileMetadata;
use SplFileInfo;

/**
 * Implementierung des DiscoveryStorage-Interfaces, die das Filesystem-Modul nutzt
 * und die Discovery-spezifischen Methoden implementiert.
 *
 * Diese Klasse verwendet Composition, um die Funktionalität des Storage-Interfaces
 * zu delegieren und erweitert sie um Discovery-spezifische Funktionen.
 */
final class FileSystemDiscoveryStorage implements DiscoveryStorage
{
    public readonly PermissionChecker $permissions;

    public readonly FiberManager $fiberManager;

    /**
     * @param Storage $storage Basis-Storage-Implementierung für Delegation
     * @param PermissionChecker $permissions Permissions-Checker
     * @param FiberManager $fiberManager Fiber-Manager für asynchrone Operationen
     */
    public function __construct(
        private readonly Storage $storage,
        ?PermissionChecker $permissions = null,
        ?FiberManager $fiberManager = null
    ) {
        $this->permissions = $permissions ?? new PermissionChecker($storage);
        $this->fiberManager = $fiberManager ?? new FiberManager(
            new \App\Framework\DateTime\SystemClock(),
            new \App\Framework\DateTime\SystemTimer()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $path): string
    {
        return $this->storage->get($path);
    }

    /**
     * {@inheritdoc}
     */
    public function put(string $path, string $content): void
    {
        $this->storage->put($path, $content);
    }

    /**
     * {@inheritdoc}
     */
    public function exists(string $path): bool
    {
        return $this->storage->exists($path);
    }

    /**
     * {@inheritdoc}
     */
    public function delete(string $path): void
    {
        $this->storage->delete($path);
    }

    /**
     * {@inheritdoc}
     */
    public function copy(string $source, string $destination): void
    {
        $this->storage->copy($source, $destination);
    }

    /**
     * {@inheritdoc}
     */
    public function size(string $path): int
    {
        return $this->storage->size($path);
    }

    /**
     * {@inheritdoc}
     */
    public function lastModified(string $path): int
    {
        return $this->storage->lastModified($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimeType(string $path): string
    {
        return $this->storage->getMimeType($path);
    }

    /**
     * {@inheritdoc}
     */
    public function isReadable(string $path): bool
    {
        return $this->storage->isReadable($path);
    }

    /**
     * {@inheritdoc}
     */
    public function isWritable(string $path): bool
    {
        return $this->storage->isWritable($path);
    }

    /**
     * {@inheritdoc}
     */
    public function listDirectory(string $directory): array
    {
        return $this->storage->listDirectory($directory);
    }

    /**
     * {@inheritdoc}
     */
    public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
    {
        $this->storage->createDirectory($path, $permissions, $recursive);
    }

    /**
     * {@inheritdoc}
     */
    public function file(string $path): File
    {
        return $this->storage->file($path);
    }

    /**
     * {@inheritdoc}
     */
    public function directory(string $path): Directory
    {
        return $this->storage->directory($path);
    }

    /**
     * {@inheritdoc}
     */
    public function batch(array $operations): array
    {
        return $this->storage->batch($operations);
    }

    /**
     * {@inheritdoc}
     */
    public function getMultiple(array $paths): array
    {
        return $this->storage->getMultiple($paths);
    }

    /**
     * {@inheritdoc}
     */
    public function putMultiple(array $files): void
    {
        $this->storage->putMultiple($files);
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadataMultiple(array $paths): array
    {
        return $this->storage->getMetadataMultiple($paths);
    }

    /**
     * {@inheritdoc}
     */
    public function findChangedFiles(string $directory, array $fileMetadata): array
    {
        // Finde alle Dateien mit dem Muster
        $allFiles = $this->findFiles($directory, '*.php');
        $changedFiles = [];
        $newFiles = [];
        $modifiedFiles = [];

        // Prüfe jede Datei auf Änderungen
        foreach ($allFiles as $file) {
            $filePath = $file->getPathname();

            // Neue Datei (nicht in Metadaten)
            if (! isset($fileMetadata[$filePath])) {
                $newFiles[] = $file;
                $changedFiles[] = $file;

                continue;
            }

            // Prüfe auf Änderungen mit detaillierten Metadaten
            try {
                $storedMetadata = FileMetadata::fromArray($fileMetadata[$filePath]);

                if ($storedMetadata->hasChanged($filePath, $this)) {
                    $modifiedFiles[] = $file;
                    $changedFiles[] = $file;
                }
            } catch (\Throwable) {
                // Bei Problemen mit Metadaten, behandle als geändert
                $changedFiles[] = $file;
            }
        }

        return $changedFiles;
    }

    /**
     * {@inheritdoc}
     */
    public function incrementalScan(string $directory, array $fileMetadata): array
    {
        $changedFiles = $this->findChangedFiles($directory, $fileMetadata);
        $updatedMetadata = $fileMetadata;

        // Aktualisiere Metadaten für geänderte Dateien
        foreach ($changedFiles as $file) {
            $filePath = $file->getPathname();

            try {
                $metadata = $this->getDiscoveryMetadata($filePath, true);
                $updatedMetadata[$filePath] = $metadata->toArray();
            } catch (\Throwable) {
                // Bei Fehlern, entferne die Metadaten
                unset($updatedMetadata[$filePath]);
            }
        }

        // Entferne gelöschte Dateien aus Metadaten
        $allFiles = $this->findFiles($directory, '*.php');
        $existingPaths = array_map(fn (SplFileInfo $file) => $file->getPathname(), $allFiles);

        foreach (array_keys($updatedMetadata) as $path) {
            if (! in_array($path, $existingPaths)) {
                unset($updatedMetadata[$path]);
            }
        }

        return $updatedMetadata;
    }

    /**
     * {@inheritdoc}
     */
    public function findFiles(string $directory, string $pattern): array
    {
        $result = [];
        $this->findFilesRecursive($directory, $pattern, $result);

        return $result;
    }

    /**
     * Rekursive Hilfsmethode für findFiles
     */
    private function findFilesRecursive(string $directory, string $pattern, array &$result): void
    {
        if (! is_dir($directory)) {
            return;
        }

        $files = scandir($directory);
        if ($files === false) {
            return;
        }

        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            $path = $directory . DIRECTORY_SEPARATOR . $file;

            if (is_dir($path)) {
                $this->findFilesRecursive($path, $pattern, $result);
            } elseif ($this->matchesPattern($file, $pattern)) {
                $result[] = new SplFileInfo($path);
            }
        }
    }

    /**
     * Prüft, ob ein Dateiname einem Muster entspricht
     */
    private function matchesPattern(string $filename, string $pattern): bool
    {
        return fnmatch($pattern, $filename);
    }

    /**
     * {@inheritdoc}
     */
    public function getDiscoveryMetadata(string $filePath, bool $calculateChecksum = true): FileMetadata
    {
        return FileMetadata::fromFile($filePath, $this, $calculateChecksum);
    }

    /**
     * {@inheritdoc}
     */
    public function getDiscoveryMetadataMultiple(array $filePaths, bool $calculateChecksum = true): array
    {
        $operations = [];

        foreach ($filePaths as $path) {
            $operations[$path] = fn () => $this->getDiscoveryMetadata($path, $calculateChecksum);
        }

        return $this->batch($operations);
    }

    /**
     * {@inheritdoc}
     */
    public function registerFileSystemEvents(string $directory, callable $callback): bool
    {
        // Implementierung hängt von der konkreten Event-System-Unterstützung ab
        // Hier eine einfache Implementierung, die immer false zurückgibt
        // In einer realen Implementierung würde hier ein Filesystem-Watcher registriert
        return false;
    }
}
