<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\Core\ValidationErrorCode;
use App\Framework\Filesystem\ValueObjects\FilePath;

/**
 * TemporaryDirectory - Fluent API für temporäre Verzeichnisse
 *
 * Framework-konforme Implementation inspiriert von spatie/temporary-directory:
 * - FilePath Value Object statt Strings
 * - Immutable operations (jede Methode gibt neue Instanz zurück wo sinnvoll)
 * - Type-safe API
 * - Explicit dependency injection
 * - Automatic cleanup support
 *
 * @example
 * // Basic usage
 * $temp = TemporaryDirectory::make()
 *     ->name('my-temp-dir')
 *     ->force()
 *     ->create();
 *
 * $filePath = $temp->path('file.txt'); // Returns FilePath
 * $temp->empty(); // Empty directory
 * $temp->delete(); // Delete directory
 *
 * @example
 * // Custom location
 * $temp = TemporaryDirectory::make()
 *     ->location(FilePath::create('/custom/temp'))
 *     ->create();
 *
 * @example
 * // Prevent auto-cleanup
 * $temp = TemporaryDirectory::make()
 *     ->doNotDeleteWhenDestruct()
 *     ->create();
 */
final class TemporaryDirectory
{
    private FilePath $location;
    private string $name;
    private bool $forceCreate = false;
    private bool $deleteWhenDestruct = true;

    private function __construct(
        FilePath $parentDirectory
    ) {
        $this->location = $parentDirectory;
        $this->name = $this->generateDirectoryName();
    }

    /**
     * Factory method für neue temporäre Verzeichnisse
     */
    public static function make(?FilePath $parentDirectory = null): self
    {
        $parentDirectory = $parentDirectory ?? FilePath::tempDir();

        return new self($parentDirectory);
    }

    /**
     * Setzt den Namen des temporären Verzeichnisses
     */
    public function name(string $name): self
    {
        $this->name = $this->sanitizeName($name);

        return $this;
    }

    /**
     * Setzt das Elternverzeichnis
     */
    public function location(FilePath $location): self
    {
        $this->location = $location;

        return $this;
    }

    /**
     * Force creation - überschreibt existierendes Verzeichnis
     */
    public function force(bool $force = true): self
    {
        $this->forceCreate = $force;

        return $this;
    }

    /**
     * Verhindere automatisches Löschen beim Destruct
     */
    public function doNotDeleteWhenDestruct(): self
    {
        $this->deleteWhenDestruct = false;

        return $this;
    }

    /**
     * Erstellt das temporäre Verzeichnis
     */
    public function create(): self
    {
        $fullPath = $this->getFullPath();

        if ($fullPath->isDirectory()) {
            if ($this->forceCreate) {
                $this->deleteDirectory($fullPath);
            } else {
                throw FrameworkException::create(
                    ValidationErrorCode::BUSINESS_RULE_VIOLATION,
                    "Directory already exists: {$fullPath}. Use force() to overwrite."
                );
            }
        }

        if (!mkdir($fullPath->toString(), 0755, true)) {
            throw FrameworkException::create(
                ValidationErrorCode::INVALID_INPUT,
                "Failed to create temporary directory: {$fullPath}"
            );
        }

        return $this;
    }

    /**
     * Gibt FilePath zum temporären Verzeichnis oder zu einer Datei darin zurück
     */
    public function path(string $pathInDirectory = ''): FilePath
    {
        $fullPath = $this->getFullPath();

        if (!$fullPath->isDirectory()) {
            throw FrameworkException::create(
                ValidationErrorCode::BUSINESS_RULE_VIOLATION,
                "Temporary directory does not exist. Call create() first."
            );
        }

        if ($pathInDirectory === '') {
            return $fullPath;
        }

        return $fullPath->join($pathInDirectory);
    }

    /**
     * Leert das temporäre Verzeichnis ohne es zu löschen
     */
    public function empty(): self
    {
        $fullPath = $this->getFullPath();

        if (!$fullPath->isDirectory()) {
            return $this;
        }

        $this->emptyDirectory($fullPath);

        return $this;
    }

    /**
     * Löscht das temporäre Verzeichnis vollständig
     */
    public function delete(): bool
    {
        $fullPath = $this->getFullPath();

        if (!$fullPath->isDirectory()) {
            return false;
        }

        return $this->deleteDirectory($fullPath);
    }

    /**
     * Prüft ob das Verzeichnis existiert
     */
    public function exists(): bool
    {
        return $this->getFullPath()->isDirectory();
    }

    /**
     * Gibt den FilePath zum temporären Verzeichnis zurück
     */
    public function getPath(): FilePath
    {
        return $this->getFullPath();
    }

    /**
     * Automatisches Cleanup beim Destruct
     */
    public function __destruct()
    {
        if ($this->deleteWhenDestruct) {
            $this->delete();
        }
    }

    /**
     * Gibt den vollständigen FilePath zum temporären Verzeichnis zurück
     */
    private function getFullPath(): FilePath
    {
        return $this->location->join($this->name);
    }

    /**
     * Generiert einen eindeutigen Verzeichnisnamen
     */
    private function generateDirectoryName(): string
    {
        return 'temp-' . bin2hex(random_bytes(8)) . '-' . time();
    }

    /**
     * Bereinigt den Verzeichnisnamen
     */
    private function sanitizeName(string $name): string
    {
        // Entferne gefährliche Zeichen
        $name = preg_replace('/[^a-zA-Z0-9_-]/', '-', $name);

        // Entferne mehrfache Bindestriche
        $name = preg_replace('/-+/', '-', $name);

        // Entferne führende/trailing Bindestriche
        return trim($name, '-');
    }

    /**
     * Leert ein Verzeichnis rekursiv
     */
    private function emptyDirectory(FilePath $path): void
    {
        $items = new \FilesystemIterator($path->toString());

        foreach ($items as $item) {
            $itemPath = FilePath::create($item->getPathname());

            if ($itemPath->isDirectory() && !$item->isLink()) {
                $this->deleteDirectory($itemPath);
            } else {
                unlink($itemPath->toString());
            }
        }
    }

    /**
     * Löscht ein Verzeichnis rekursiv
     */
    private function deleteDirectory(FilePath $path): bool
    {
        if (!$path->isDirectory()) {
            return false;
        }

        $this->emptyDirectory($path);

        return rmdir($path->toString());
    }
}
