<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

/**
 * Zentraler Filesystem-Manager mit verschiedenen Storage-Backends und Serializer-Support
 *
 * Ermöglicht die Verwendung verschiedener Storage-Implementierungen
 * und Serializer für verschiedene Datenformate.
 */
final class FilesystemManager
{
    /** @var array<string, Storage> */
    private array $storages;

    private SerializerRegistry $serializerRegistry;

    public function __construct(
        private string $defaultStorageName = 'default',
        ?SerializerRegistry $serializerRegistry = null
    ) {
        $this->storages = [];
        $this->serializerRegistry = $serializerRegistry ?? SerializerRegistry::createDefault();
    }

    /**
     * Holt ein Storage-Backend by Name
     */
    public function storage(string $name = ''): Storage
    {
        $storageName = $name ?: $this->defaultStorageName;

        if (! isset($this->storages[$storageName])) {
            throw new \InvalidArgumentException("Storage '{$storageName}' not found");
        }

        return $this->storages[$storageName];
    }

    /**
     * Registriert ein neues Storage-Backend
     */
    public function registerStorage(string $name, Storage $storage): void
    {
        $this->storages[$name] = $storage;
    }

    /**
     * Registriert einen Serializer
     */
    public function registerSerializer(string $name, Serializer $serializer, bool $setAsDefault = false): void
    {
        $this->serializerRegistry->register($name, $serializer, $setAsDefault);
    }

    /**
     * Holt einen Serializer by Name
     */
    public function getSerializer(string $name): Serializer
    {
        return $this->serializerRegistry->get($name);
    }

    /**
     * Holt SerializerRegistry
     */
    public function getSerializerRegistry(): SerializerRegistry
    {
        return $this->serializerRegistry;
    }

    // === Basic Storage Operations ===

    public function get(string $path, string $storage = ''): string
    {
        return $this->storage($storage)->get($path);
    }

    public function put(string $path, string $content, string $storage = ''): void
    {
        $this->storage($storage)->put($path, $content);
    }

    public function exists(string $path, string $storage = ''): bool
    {
        return $this->storage($storage)->exists($path);
    }

    public function delete(string $path, string $storage = ''): void
    {
        $this->storage($storage)->delete($path);
    }

    public function copy(string $source, string $destination, string $storage = ''): void
    {
        $this->storage($storage)->copy($source, $destination);
    }

    // === Serialized Storage Operations ===

    /**
     * Speichert Daten mit einem Serializer
     */
    public function putSerialized(string $path, mixed $data, Serializer $serializer, string $storage = ''): void
    {
        $content = $serializer->serialize($data);
        $this->storage($storage)->put($path, $content);
    }

    /**
     * Lädt und deserialisiert Daten
     */
    public function getSerialized(string $path, Serializer $serializer, string $storage = ''): mixed
    {
        $content = $this->storage($storage)->get($path);

        return $serializer->deserialize($content);
    }

    /**
     * Auto-Detect: Speichert Daten mit automatisch erkanntem Serializer basierend auf Dateiendung
     */
    public function putAuto(string $path, mixed $data, string $storage = ''): void
    {
        $serializer = $this->serializerRegistry->detectFromPath($path);
        $this->putSerialized($path, $data, $serializer, $storage);
    }

    /**
     * Auto-Detect: Lädt Daten mit automatisch erkanntem Serializer basierend auf Dateiendung
     */
    public function getAuto(string $path, string $storage = ''): mixed
    {
        $serializer = $this->serializerRegistry->detectFromPath($path);

        return $this->getSerialized($path, $serializer, $storage);
    }

    // === Convenience Methods für häufig verwendete Serializer ===

    /**
     * JSON-Operationen
     *
     * @param array<string, mixed> $data
     */
    public function putJson(string $path, array $data, string $storage = ''): void
    {
        $this->putSerialized($path, $data, $this->getSerializer('json'), $storage);
    }

    /**
     * @return array<string, mixed>
     */
    public function getJson(string $path, string $storage = ''): array
    {
        return $this->getSerialized($path, $this->getSerializer('json'), $storage);
    }

    /**
     * CSV-Operationen (wenn CSV-Serializer registriert ist)
     *
     * @param array<int, array<string, mixed>> $data
     */
    public function putCsv(string $path, array $data, string $storage = ''): void
    {
        $this->putSerialized($path, $data, $this->getSerializer('csv'), $storage);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function getCsv(string $path, string $storage = ''): array
    {
        return $this->getSerialized($path, $this->getSerializer('csv'), $storage);
    }

    // === Extended Operations ===

    /**
     * Atomic Write (falls vom Storage unterstützt)
     */
    public function putAtomic(string $path, string $content, string $storage = ''): void
    {
        $storageInstance = $this->storage($storage);

        // Erweiterte Storage-Implementierungen können AtomicStorage Interface implementieren
        if (method_exists($storageInstance, 'putAtomic')) {
            $storageInstance->putAtomic($path, $content);
        } else {
            // Fallback: Normale put-Operation
            $storageInstance->put($path, $content);
        }
    }

    /**
     * Append-Operation (falls vom Storage unterstützt)
     */
    public function append(string $path, string $content, string $storage = ''): void
    {
        $storageInstance = $this->storage($storage);

        if (method_exists($storageInstance, 'append')) {
            $storageInstance->append($path, $content);
        } else {
            // Fallback: Lesen, anhängen, schreiben
            $existing = $storageInstance->exists($path) ? $storageInstance->get($path) : '';
            $storageInstance->put($path, $existing . $content);
        }
    }

    // === Storage Management ===

    /**
     * Multi-Storage Operationen
     */
    public function copyBetweenStorages(string $source, string $destination, string $sourceStorage = '', string $destStorage = ''): void
    {
        $sourceStorageName = $sourceStorage ?: $this->defaultStorageName;
        $destStorageName = $destStorage ?: $this->defaultStorageName;

        $content = $this->storage($sourceStorageName)->get($source);
        $this->storage($destStorageName)->put($destination, $content);
    }

    /**
     * Storage-Statistiken
     *
     * @return array<string, array<string, mixed>>
     */
    public function getStorageInfo(): array
    {
        /** @var array<string, array<string, mixed>> */
        $info = [];

        foreach ($this->storages as $name => $storage) {
            $info[$name] = [
                'type' => get_class($storage),
                'available' => true, // Könnte erweitert werden für Health-Checks
            ];
        }

        return $info;
    }

    /**
     * Serializer-Statistiken
     *
     * @return array<string, array<string, string>>
     */
    public function getSerializerInfo(): array
    {
        /** @var array<string, array<string, string>> */
        $info = [];

        foreach ($this->serializers as $name => $serializer) {
            $info[$name] = [
                'type' => get_class($serializer),
                'mime_type' => $serializer->getMimeType(),
                'extension' => $serializer->getFileExtension(),
            ];
        }

        return $info;
    }

    /**
     * Factory-Methode für Standard-Setup
     *
     * @param array<string, mixed> $config
     */
    public static function create(array $config = []): self
    {
        $defaultStorage = 'default';
        /** @var array<string, Storage> */
        $namedStorages = [];

        $manager = new self($defaultStorage, $namedStorages);

        // Weitere Standard-Serializer können hier registriert werden
        if (isset($config['serializers'])) {
            foreach ($config['serializers'] as $name => $serializer) {
                $manager->registerSerializer($name, $serializer);
            }
        }

        return $manager;
    }
}
