<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Filesystem;

use Flowaxy\Contracts\Filesystem\DirectoryInterface;
use Flowaxy\Support\Facades\Log;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

use function array_diff;
use function chmod;
use function copy;
use function count;
use function dirname;
use function file_exists;
use function filesize;
use function floor;
use function is_dir;
use function is_file;
use function is_readable;
use function is_writable;
use function log;
use function max;
use function min;
use function mkdir;
use function rename;
use function rmdir;
use function round;
use function rtrim;
use function scandir;
use function sprintf;
use function unlink;
use const DS;

// Клас для роботи з директоріями
// Створення, видалення, копіювання директорій та робота з файлами всередині
final class Directory implements DirectoryInterface
{
    private string $path;

    // Конструктор
    public function __construct(?string $path = null)
    {
        if ($path !== null) {
            $this->setPath($path);
        }
    }

    // Встановлення шляху до директорії
    public function setPath(string $path): self
    {
        $this->path = rtrim($path, '/\\') . DS;

        return $this;
    }

    // Отримання шляху до директорії
    public function getPath(): string
    {
        return $this->path;
    }

    // Перевірити існування директорії
    public function exists(string $path): bool
    {
        return is_dir($path);
    }

    // Перевірка, чи є директорія читабельною
    public function isReadable(): bool
    {
        return $this->exists($this->path) && is_readable($this->path);
    }

    // Перевірка, чи є директорія доступною для запису
    public function isWritable(): bool
    {
        return $this->exists($this->path) && is_writable($this->path);
    }

    // Створити директорію
    public function create(string $path, int $mode = 0755, bool $recursive = false): bool
    {
        if ($this->exists($path)) {
            Log::Debug('Directory::create: Directory already exists', ['path' => $path]);
            return true;
        }

        if (! @mkdir($path, $mode, $recursive)) {
            Log::Error('Directory::create: Failed to create directory', [
                'path' => $path,
                'mode' => sprintf('%o', $mode),
            ]);
            return false;
        }

        Log::Info('Directory::create: Directory created successfully', [
            'path' => $path,
            'mode' => sprintf('%o', $mode),
        ]);

        return true;
    }

    // Видалити директорію
    public function delete(string $path, bool $preserve = false): bool
    {
        Log::debug('Directory::delete: Deleting directory', [
            'path' => $path,
            'preserve' => $preserve,
        ]);

        if (! $this->exists($path)) {
            Log::Debug('Directory::delete: Directory does not exist', ['path' => $path]);
            return true;
        }

        if ($preserve) {
            // Просто видаляємо порожню директорію
            if (! @rmdir($path)) {
                Log::Error('Directory::delete: Failed to delete directory', ['path' => $path]);
                return false;
            }

            Log::Info('Directory::delete: Directory deleted successfully', ['path' => $path]);
            return true;
        }

        // Рекурсивне видалення
        $result = $this->removeRecursive($path);
        if ($result) {
            Log::Info('Directory::delete: Directory deleted recursively', ['path' => $path]);
        } else {
            Log::Error('Directory::delete: Failed to delete directory recursively', ['path' => $path]);
        }

        return $result;
    }

    // Рекурсивне видалення директорії
    private function removeRecursive(string $dir): bool
    {
        if (! is_dir($dir)) {
            return false;
        }

        $files = array_diff(scandir($dir), ['.', '..']);

        foreach ($files as $file) {
            $filePath = $dir . DS . $file;

            if (is_dir($filePath)) {
                $this->removeRecursive($filePath);
            } else {
                @unlink($filePath);
            }
        }

        return @rmdir($dir);
    }

    // Копіювати директорію
    public function copy(string $from, string $to): bool
    {
        if (! $this->exists($from)) {
            Log::Error('Directory::copy: Source directory does not exist', ['from' => $from]);
            return false;
        }

        $to = rtrim($to, '/\\') . DS;

        // Створюємо директорію призначення
        if (! is_dir($to)) {
            if (! @mkdir($to, 0755, true)) {
                Log::Error('Directory::copy: Failed to create destination directory', ['to' => $to]);
                return false;
            }
        }

        return $this->copyRecursive($from, $to, true);
    }

    // Рекурсивне копіювання
    private function copyRecursive(string $source, string $destination, bool $overwrite): bool
    {
        if (! is_dir($source)) {
            return false;
        }

        if (! is_dir($destination)) {
            if (! @mkdir($destination, 0755, true)) {
                return false;
            }
        }

        $files = array_diff(scandir($source), ['.', '..']);

        foreach ($files as $file) {
            $sourcePath = $source . $file;
            $destinationPath = $destination . $file;

            if (is_dir($sourcePath)) {
                if (! $this->copyRecursive($sourcePath . DS, $destinationPath . DS, $overwrite)) {
                    return false;
                }
            } else {
                if (! $overwrite && file_exists($destinationPath)) {
                    continue;
                }

                if (! @copy($sourcePath, $destinationPath)) {
                    return false;
                }
            }
        }

        return true;
    }

    // Отримати всі файли в директорії
    public function files(string $path, bool $hidden = false): array
    {
        if (! $this->exists($path)) {
            return [];
        }

        $files = [];
        $items = scandir($path);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            if (! $hidden && $item[0] === '.') {
                continue;
            }

            $filePath = $path . DS . $item;

            if (is_file($filePath)) {
                $files[] = $filePath;
            }
        }

        return $files;
    }

    // Отримати всі директорії в директорії
    public function directories(string $path): array
    {
        if (! $this->exists($path)) {
            return [];
        }

        $directories = [];
        $items = scandir($path);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $itemPath = $path . DS . $item;

            if (is_dir($itemPath)) {
                $directories[] = $itemPath;
            }
        }

        return $directories;
    }

    // Отримати всі елементи в директорії
    public function all(string $path): array
    {
        if (! $this->exists($path)) {
            return [];
        }

        $all = [];
        $items = scandir($path);

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $all[] = $path . DS . $item;
        }

        return $all;
    }

    // Перемістити директорію
    public function move(string $from, string $to): bool
    {
        if (! $this->exists($from)) {
            Log::Error('Directory::move: Source directory does not exist', ['from' => $from]);
            return false;
        }

        // Створюємо батьківську директорію, якщо її немає
        $parentDir = dirname($to);
        if (! is_dir($parentDir)) {
            if (! @mkdir($parentDir, 0755, true)) {
                Log::Error('Directory::move: Failed to create parent directory', ['parent' => $parentDir]);
                return false;
            }
        }

        if (! @rename($from, $to)) {
            Log::Error('Directory::move: Failed to move directory', ['from' => $from, 'to' => $to]);
            return false;
        }

        Log::Info('Directory::move: Directory moved successfully', ['from' => $from, 'to' => $to]);
        return true;
    }

    // Отримання розміру директорії
    public function getSize(bool $format = false)
    {
        if (! $this->exists($this->path)) {
            return $format ? '0 B' : 0;
        }

        $size = 0;
        $files = $this->files($this->path);
        // Для рекурсивного підрахунку використовуємо ітератор
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this->path, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $files[] = $file->getRealPath();
            }
        }

        foreach ($files as $file) {
            if (file_exists($file)) {
                $size += filesize($file);
            }
        }

        if ($format) {
            return $this->formatSize($size);
        }

        return $size;
    }

    // Форматування розміру
    private function formatSize(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= (1 << (10 * $pow));

        return round($bytes, 2) . ' ' . $units[$pow];
    }

    // Очищення директорії (видалення всіх файлів та піддиректорій)
    public function clean(): bool
    {
        if (! $this->exists($this->path)) {
            return true;
        }

        return $this->delete($this->path, false) && $this->create($this->path);
    }

    // Встановлення прав доступу до директорії
    public function chmod(int $mode): bool
    {
        if (! $this->exists($this->path)) {
            return false;
        }

        return @chmod($this->path, $mode);
    }

    // Статичний метод: Створення директорії
    public static function make(string $path, int $mode = 0755, bool $recursive = true): bool
    {
        $dir = new self($path);

        try {
            return $dir->create($path, $mode, $recursive);
        } catch (\Exception $e) {
            Log::Error('Directory::make помилка: ' . $e->getMessage(), ['exception' => $e, 'path' => $path]);
            return false;
        }
    }

    // Статичний метод: Видалення директорії
    public static function remove(string $path, bool $recursive = true): bool
    {
        $dir = new self($path);

        try {
            return $dir->delete($path, !$recursive);
        } catch (\Exception $e) {
            Log::Error('Directory::remove помилка: ' . $e->getMessage(), ['exception' => $e, 'path' => $path]);
            return false;
        }
    }

    // Статичний метод: Копіювання директорії
    public static function copyDirectory(string $sourcePath, string $destinationPath, bool $overwrite = true): bool
    {
        $dir = new self($sourcePath);

        try {
            return $dir->copy($sourcePath, $destinationPath);
        } catch (\Exception $e) {
            Log::Error('Directory::copyDirectory помилка: ' . $e->getMessage(), ['exception' => $e, 'source' => $sourcePath, 'destination' => $destinationPath]);
            return false;
        }
    }

    // Статичний метод: Перевірка існування директорії
    public static function directoryExists(string $path): bool
    {
        return is_dir($path);
    }
}
