<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Async\FiberManager;
use App\Framework\Filesystem\Exceptions\DirectoryCreateException;
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
use App\Framework\Filesystem\Exceptions\FileReadException;
use App\Framework\Filesystem\Traits\InMemoryStreamableStorageTrait;
use App\Framework\Filesystem\ValueObjects\FileMetadata;

/**
 * In-Memory-Implementierung des Storage-Interfaces für Tests
 */
final class InMemoryStorage implements Storage, StreamableStorage
{
    use InMemoryStreamableStorageTrait;

    /** @var array<string, string> */
    private array $files = [];

    /** @var array<string, bool> */
    private array $directories = [];

    /** @var array<string, int> */
    private array $timestamps = [];

    public readonly PermissionChecker $permissions;

    public readonly FiberManager $fiberManager;

    public function __construct()
    {
        $this->permissions = new PermissionChecker($this);
        $this->fiberManager = $this->createDefaultFiberManager();
    }

    private function createDefaultFiberManager(): FiberManager
    {
        // Create minimal dependencies for FiberManager
        $clock = new \App\Framework\DateTime\SystemClock();
        $timer = new \App\Framework\DateTime\SystemTimer($clock);

        return new FiberManager($clock, $timer);
    }

    // Methods required by InMemoryStreamableStorageTrait

    /**
     * @return array<string, string>
     */
    protected function getFilesArray(): array
    {
        return $this->files;
    }

    protected function setFileContent(string $path, string $content): void
    {
        $this->files[$path] = $content;
        $this->timestamps[$path] = time();
    }

    public function get(string $path): string
    {
        if (! isset($this->files[$path])) {
            throw new FileNotFoundException($path);
        }

        return $this->files[$path];
    }

    public function put(string $path, string $content): void
    {
        $dir = dirname($path);
        if (! isset($this->directories[$dir])) {
            $this->createDirectory($dir);
        }

        $this->files[$path] = $content;
        $this->timestamps[$path] = time();
    }

    public function exists(string $path): bool
    {
        return isset($this->files[$path]);
    }

    public function delete(string $path): void
    {
        if (! isset($this->files[$path])) {
            throw new FileNotFoundException($path);
        }

        unset($this->files[$path]);
        unset($this->timestamps[$path]);
    }

    public function copy(string $source, string $destination): void
    {
        if (! isset($this->files[$source])) {
            throw new FileNotFoundException($source);
        }

        $dir = dirname($destination);
        if (! isset($this->directories[$dir])) {
            $this->createDirectory($dir);
        }

        $this->files[$destination] = $this->files[$source];
        $this->timestamps[$destination] = time();
    }

    public function size(string $path): int
    {
        if (! isset($this->files[$path])) {
            throw new FileNotFoundException($path);
        }

        return strlen($this->files[$path]);
    }

    public function lastModified(string $path): int
    {
        if (! isset($this->files[$path])) {
            throw new FileNotFoundException($path);
        }

        return $this->timestamps[$path] ?? time();
    }

    public function listDirectory(string $directory): array
    {
        if (! isset($this->directories[$directory])) {
            throw new DirectoryCreateException($directory);
        }

        $result = [];
        $prefix = $directory . DIRECTORY_SEPARATOR;
        $prefixLength = strlen($prefix);

        // Dateien im Verzeichnis suchen
        foreach (array_keys($this->files) as $path) {
            if (str_starts_with($path, $prefix)) {
                // Nur direkte Kinder, keine tieferen Ebenen
                $relativePath = substr($path, $prefixLength);
                if (! str_contains($relativePath, DIRECTORY_SEPARATOR)) {
                    $result[] = $path;
                }
            }
        }

        // Verzeichnisse im Verzeichnis suchen
        foreach (array_keys($this->directories) as $path) {
            if ($path !== $directory && strpos($path, $prefix) === 0) {
                // Nur direkte Kinder
                $relativePath = substr($path, $prefixLength);
                if (! str_contains($relativePath, DIRECTORY_SEPARATOR)) {
                    $result[] = $path;
                }
            }
        }

        return $result;
    }

    public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
    {
        if (isset($this->directories[$path])) {
            return; // Verzeichnis existiert bereits
        }

        if ($recursive) {
            // Übergeordnete Verzeichnisse erstellen
            $parent = dirname($path);
            if ($parent !== '.' && $parent !== '/' && $parent !== $path) {
                $this->createDirectory($parent, $permissions, true);
            }
        }

        $this->directories[$path] = true;
    }

    /**
     * Fügt eine Datei direkt zum In-Memory-Speicher hinzu (für Tests)
     */
    public function addFile(string $path, string $content): void
    {
        $dir = dirname($path);
        $this->createDirectory($dir);
        $this->files[$path] = $content;
        $this->timestamps[$path] = time();
    }

    /**
     * Löscht alle Daten im Speicher (für Tests)
     */
    public function clear(): void
    {
        $this->files = [];
        $this->directories = [];
        $this->timestamps = [];
    }

    /**
     * Erstellt ein File-Objekt mit Lazy-Loading für teure Eigenschaften
     */
    public function file(string $path): File
    {
        return FilesystemFactory::createFile($path, $this);
    }

    /**
     * Erstellt ein Directory-Objekt mit Lazy-Loading für teure Eigenschaften
     */
    public function directory(string $path): Directory
    {
        return FilesystemFactory::createDirectory($path, $this);
    }

    public function getMimeType(string $path): string
    {
        if (! isset($this->files[$path])) {
            throw new FileNotFoundException($path);
        }

        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        if ($finfo === false) {
            throw new FileReadException($path);
        }

        $mimeType = finfo_buffer($finfo, $this->files[$path]);
        finfo_close($finfo);

        return $mimeType ?: 'application/octet-stream';
    }

    public function isReadable(string $path): bool
    {
        return true; // Alle Dateien sind im In-Memory-Speicher lesbar
    }

    public function isWritable(string $path): bool
    {
        return true;
    }

    public function batch(array $operations): array
    {
        return $this->fiberManager->batch($operations);
    }

    public function getMultiple(array $paths): array
    {
        $operations = [];
        foreach ($paths as $path) {
            $operations[$path] = fn () => $this->get($path);
        }

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

    public function putMultiple(array $files): void
    {
        $operations = [];
        foreach ($files as $path => $content) {
            $operations[$path] = fn () => $this->put($path, $content);
        }
        $this->batch($operations);
    }

    public function getMetadataMultiple(array $paths): array
    {
        $operations = [];
        foreach ($paths as $path) {
            $operations[$path] = fn () => new FileMetadata(
                path: $path,
                size: $this->size($path),
                lastModified: $this->lastModified($path),
                mimeType: $this->getMimeType($path),
                isReadable: $this->isReadable($path),
                isWritable: $this->isWritable($path)
            );
        }

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