<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Core\PathProvider;
use App\Framework\Filesystem\ValueObjects\FilePermissions;

/**
 * Helper-Klasse für Filesystem-Permissions-Checks
 */
final readonly class PermissionChecker
{
    public function __construct(
        private PathProvider $pathProvider
    ) {
    }

    private function resolvePath(string $path): string
    {
        // Absolute paths bleiben unverändert
        if (str_starts_with($path, '/')) {
            return $path;
        }

        // Relative paths über PathProvider auflösen (mit Caching)
        return $this->pathProvider->resolvePath($path);
    }

    public function isDirectoryWritable(string $path): bool
    {
        $resolvedPath = $this->resolvePath($path);
        if (! is_dir($resolvedPath)) {
            return false;
        }

        return is_writable($resolvedPath);
    }

    public function canCreateDirectory(string $path): bool
    {
        $resolvedPath = $this->resolvePath($path);
        // Prüfe, ob Parent-Directory existiert und schreibbar ist
        $parentDir = dirname($resolvedPath);

        if (! is_dir($parentDir)) {
            return $this->canCreateDirectory(dirname($path));
        }

        return is_writable($parentDir);
    }

    public function canWriteFile(string $path): bool
    {
        $resolvedPath = $this->resolvePath($path);
        // Wenn Datei existiert, prüfe ob sie schreibbar ist
        if (file_exists($resolvedPath)) {
            return is_writable($resolvedPath);
        }

        // Wenn Datei nicht existiert, prüfe ob Directory schreibbar ist
        $dir = dirname($path);

        return $this->isDirectoryWritable($dir) || $this->canCreateDirectory($dir);
    }

    public function canReadFile(string $path): bool
    {
        $resolvedPath = $this->resolvePath($path);

        return file_exists($resolvedPath) && is_readable($resolvedPath);
    }

    public function canDeleteFile(string $path): bool
    {
        $resolvedPath = $this->resolvePath($path);
        if (! file_exists($resolvedPath)) {
            return false;
        }

        // Datei muss schreibbar sein UND Directory muss schreibbar sein
        $dir = dirname($resolvedPath);

        return is_writable($resolvedPath) && is_writable($dir);
    }

    public function getPermissionString(string $path): string
    {
        $resolvedPath = $this->resolvePath($path);
        if (! file_exists($resolvedPath)) {
            return 'not found';
        }

        $perms = fileperms($resolvedPath);
        $info = '';

        // Type
        if (($perms & 0xC000) == 0xC000) {
            $info = 's'; // Socket
        } elseif (($perms & 0xA000) == 0xA000) {
            $info = 'l'; // Symbolic Link
        } elseif (($perms & 0x8000) == 0x8000) {
            $info = 'f'; // Regular file
        } elseif (($perms & 0x6000) == 0x6000) {
            $info = 'b'; // Block special
        } elseif (($perms & 0x4000) == 0x4000) {
            $info = 'd'; // Directory
        } elseif (($perms & 0x2000) == 0x2000) {
            $info = 'c'; // Character special
        } elseif (($perms & 0x1000) == 0x1000) {
            $info = 'p'; // FIFO pipe
        } else {
            $info = 'u'; // Unknown
        }

        // Owner
        $info .= (($perms & 0x0100) ? 'r' : '-');
        $info .= (($perms & 0x0080) ? 'w' : '-');
        $info .= (($perms & 0x0040) ?
            (($perms & 0x0800) ? 's' : 'x') :
            (($perms & 0x0800) ? 'S' : '-'));

        // Group
        $info .= (($perms & 0x0020) ? 'r' : '-');
        $info .= (($perms & 0x0010) ? 'w' : '-');
        $info .= (($perms & 0x0008) ?
            (($perms & 0x0400) ? 's' : 'x') :
            (($perms & 0x0400) ? 'S' : '-'));

        // World
        $info .= (($perms & 0x0004) ? 'r' : '-');
        $info .= (($perms & 0x0002) ? 'w' : '-');
        $info .= (($perms & 0x0001) ?
            (($perms & 0x0200) ? 't' : 'x') :
            (($perms & 0x0200) ? 'T' : '-'));

        return $info;
    }

    public function getDiagnosticInfo(string $path): array
    {
        $resolvedPath = $this->resolvePath($path);
        $realPath = realpath($resolvedPath) ?: $resolvedPath;

        return [
            'path' => $path,
            'resolved_path' => $resolvedPath,
            'real_path' => $realPath,
            'exists' => file_exists($resolvedPath),
            'is_file' => is_file($resolvedPath),
            'is_dir' => is_dir($resolvedPath),
            'is_readable' => is_readable($resolvedPath),
            'is_writable' => is_writable($resolvedPath),
            'permissions' => $this->getPermissionString($path),
            'owner' => file_exists($resolvedPath) ? posix_getpwuid(fileowner($resolvedPath))['name'] ?? 'unknown' : null,
            'group' => file_exists($resolvedPath) ? posix_getgrgid(filegroup($resolvedPath))['name'] ?? 'unknown' : null,
            'parent_dir' => dirname($resolvedPath),
            'parent_writable' => is_dir(dirname($resolvedPath)) && is_writable(dirname($resolvedPath)),
        ];
    }

    /**
     * Get current file permissions as FilePermissions value object
     */
    public function getPermissions(string $path): ?FilePermissions
    {
        $resolvedPath = $this->resolvePath($path);
        if (! file_exists($resolvedPath)) {
            return null;
        }

        $perms = fileperms($resolvedPath);
        // Extract only permission bits (last 9 bits)
        $mode = $perms & 0777;

        return new FilePermissions($mode);
    }

    /**
     * Set file permissions using FilePermissions value object
     */
    public function setPermissions(string $path, FilePermissions $permissions): bool
    {
        $resolvedPath = $this->resolvePath($path);
        if (! file_exists($resolvedPath)) {
            return false;
        }

        return @chmod($resolvedPath, $permissions->mode);
    }

    /**
     * Ensure file has specific permissions, set them if different
     */
    public function ensurePermissions(string $path, FilePermissions $permissions): bool
    {
        $current = $this->getPermissions($path);

        if ($current === null) {
            return false; // File doesn't exist
        }

        if ($current->equals($permissions)) {
            return true; // Already has correct permissions
        }

        return $this->setPermissions($path, $permissions);
    }
}
