<?php

/**
 * Сервис для извлечения ZIP архивов
 * Общая логика извлечения архивов для плагинов и тем
 *
 * @package Flowaxy\Support\Services
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Services;

use Exception;
use Flowaxy\Support\Facades\Archive;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\FileHelper;
use Flowaxy\Support\Helpers\JsonHelper;
use Flowaxy\Support\Helpers\ResourceCleanupHelper;
use Throwable;
use ZipArchive;

use function array_filter;
use function basename;
use function dirname;
use function explode;
use function is_array;
use function is_string;
use function preg_match;
use function str_replace;
use function trim;

final class ArchiveExtractionService
{
    /**
     * Извлечение ZIP архива
     *
     * @param string $zipPath Путь к ZIP архиву
     * @param string $destinationPath Путь назначения
     * @param string $configFileName Имя конфигурационного файла (Plugin.php, Theme.php)
     * @param bool $overwrite Перезаписать существующие файлы
     * @return array{success: bool, slug?: string, extracted?: int, error?: string, root_path?: string|null}
     */
    public function extractZip(
        string $zipPath,
        string $destinationPath,
        string $configFileName = 'Plugin.php',
        bool $overwrite = false
    ): array {
        $zip = null;
        $uploadedFile = $zipPath;

        try {
            // Открываем ZIP архив
            $zip = Archive::open($zipPath, \ZipArchive::RDONLY);
            if (!$zip) {
                return [
                    'success' => false,
                    'error' => 'Не вдалося відкрити ZIP архів',
                ];
            }

            // Валидация архива
            $validation = $this->validateArchive($zip, $configFileName);
            if (!$validation['valid']) {
                $this->cleanup($zip, $uploadedFile);
                return [
                    'success' => false,
                    'error' => $validation['error'] ?? 'Архів не валідний',
                ];
            }

            $configPath = $validation['config_path'] ?? null;
            $slug = $validation['slug'] ?? null;
            $rootPath = $this->findRootPath($zip, $configPath);

            // Проверяем, существует ли уже директория
            if (FileHelper::isDirectory($destinationPath)) {
                if ($overwrite) {
                    ResourceCleanupHelper::deleteDirectory($destinationPath);
                } else {
                    $this->cleanup($zip, $uploadedFile);
                    return [
                        'success' => false,
                        'error' => 'Директорія вже існує: ' . basename($destinationPath),
                    ];
                }
            }

            // Создаем директорию назначения
            if (!FileHelper::makeDirectory($destinationPath, 0755, true)) {
                $this->cleanup($zip, $uploadedFile);
                return [
                    'success' => false,
                    'error' => 'Помилка створення директорії призначення',
                ];
            }

            // Извлекаем файлы
            $extracted = $this->extractFiles($zip, $destinationPath, $rootPath);

            // Закрываем архив и очищаем временный файл
            $this->cleanup($zip, $uploadedFile);

            return [
                'success' => true,
                'slug' => $slug,
                'extracted' => $extracted,
                'root_path' => $rootPath,
            ];
        } catch (Throwable $e) {
            $this->cleanup($zip, $uploadedFile);
            try {
                Log::Error('Помилка витягування архіву', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return [
                'success' => false,
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Валидация архива
     *
     * @param object $zip Объект ZIP архива
     * @param string $configFileName Имя конфигурационного файла
     * @return array{valid: bool, config_path?: string|null, slug?: string|null, error?: string}
     */
    public function validateArchive(object $zip, string $configFileName): array
    {
        try {
            $entries = $zip->listFiles();
            if (empty($entries)) {
                return [
                    'valid' => false,
                    'error' => 'Архів порожній',
                ];
            }

            $hasConfig = false;
            $configPath = null;
            $slug = null;

            foreach ($entries as $entryName) {
                // Нормализуем путь
                $normalizedPath = str_replace('\\', '/', $entryName);
                $normalizedPath = trim($normalizedPath, '/');

                // Пропускаем директории
                if (str_ends_with($normalizedPath, '/')) {
                    continue;
                }

                // Проверяем наличие конфигурационного файла (нечувствительно к регистру)
                $entryBasename = basename($normalizedPath);
                if (strcasecmp($entryBasename, $configFileName) === 0) {
                    $hasConfig = true;
                    $configPath = $entryName;

                    // Определяем slug из пути
                    $pathParts = explode('/', $normalizedPath);
                    if (count($pathParts) >= 2) {
                        $slug = $this->cleanGitHubSuffix($pathParts[0]);
                    }

                    break;
                }
            }

            if (!$hasConfig) {
                return [
                    'valid' => false,
                    'error' => "Архів не містить {$configFileName}",
                ];
            }

            // Если slug не определен из пути, пытаемся получить из PHPDoc конфига
            if (!$slug && $configPath) {
                $configContent = $zip->getFromName($configPath);
                if ($configContent) {
                    // Парсим PHPDoc для получения slug
                    if (preg_match('/@slug:\s*([^\n\*@]+?)(?=\s*\*\/|\s*\*?\s*@|\s*$)/is', $configContent, $matches)) {
                        $slug = trim($matches[1]);
                        // Убираем звездочки и пробелы
                        $slug = preg_replace('/^\s*\*\s*/m', '', $slug);
                        $slug = preg_replace('/\s+/', ' ', $slug);
                        $slug = trim($slug);
                    }

                    // Если не нашли через regex, пробуем построчно
                    if (empty($slug)) {
                        $lines = explode("\n", $configContent);
                        foreach ($lines as $line) {
                            $line = trim($line);
                            $line = preg_replace('/^\s*\*\s*/', '', $line);
                            if (preg_match('/^@slug:\s*(.+)$/i', $line, $m)) {
                                $slug = trim($m[1]);
                                break;
                            }
                        }
                    }
                }
            }

            // Валидация slug
            if ($slug) {
                $slug = preg_replace('/[^a-z0-9\-_]/i', '', $slug);
            }

            if (empty($slug)) {
                return [
                    'valid' => false,
                    'error' => 'Неможливо визначити slug',
                ];
            }

            return [
                'valid' => true,
                'config_path' => $configPath,
                'slug' => $slug,
            ];
        } catch (Throwable $e) {
            return [
                'valid' => false,
                'error' => 'Помилка валідації архіву: ' . $e->getMessage(),
            ];
        }
    }

    /**
     * Поиск корневой папки в архиве
     *
     * @param object $zip Объект ZIP архива
     * @param string|null $configPath Путь к конфигурационному файлу
     * @return string|null
     */
    public function findRootPath(object $zip, ?string $configPath): ?string
    {
        if (!$configPath) {
            return null;
        }

        $rootPath = dirname($configPath);
        if ($rootPath === '.' || $rootPath === '' || $rootPath === '\\') {
            return null;
        }

        return $rootPath;
    }

    /**
     * Извлечение файлов из архива
     *
     * @param object $zip Объект ZIP архива
     * @param string $destinationPath Путь назначения
     * @param string|null $rootPath Корневая папка в архиве
     * @return int Количество извлеченных файлов
     */
    public function extractFiles(object $zip, string $destinationPath, ?string $rootPath = null): int
    {
        $extracted = 0;
        $entries = $zip->listFiles();

        foreach ($entries as $entryName) {
            if (str_ends_with($entryName, '/')) {
                continue;
            }

            // Определяем относительный путь
            $relativePath = null;
            if ($rootPath) {
                if (str_starts_with($entryName, $rootPath . '/')) {
                    $relativePath = substr($entryName, strlen($rootPath) + 1);
                } else {
                    continue;
                }
            } else {
                $relativePath = $entryName;
            }

            // Проверка на path traversal
            if (str_contains($relativePath, '../') || str_contains($relativePath, '..\\')) {
                continue;
            }

            $targetPath = $destinationPath . DS . $relativePath;
            $targetDirPath = dirname($targetPath);

            // Создаем директорию, если нужно
            if (!FileHelper::isDirectory($targetDirPath)) {
                if (!FileHelper::makeDirectory($targetDirPath, 0755, true)) {
                    try {
                        Log::Warning("Помилка створення директорії", [
                            'directory' => $targetDirPath,
                            'exception' => new Exception("Failed to create directory: {$targetDirPath}"),
                        ]);
                    } catch (Throwable $e) {
                        // Ignore logging errors
                    }
                    continue;
                }
            }

            // Извлекаем файл
            try {
                $zip->extractFile($entryName, $targetPath);
                $extracted++;
            } catch (Exception $e) {
                try {
                    Log::Error("Помилка витягування файлу {$entryName}", ['exception' => $e, 'entry' => $entryName]);
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
            }
        }

        return $extracted;
    }

    /**
     * Очистка ресурсов
     *
     * @param object|null $zip Объект ZIP архива
     * @param string|null $uploadedFile Путь к загруженному файлу
     * @return void
     */
    public function cleanup(?object $zip = null, ?string $uploadedFile = null): void
    {
        if ($zip && method_exists($zip, 'close')) {
            try {
                $zip->close();
            } catch (Exception $e) {
                // Игнорируем ошибки закрытия
            }
        }

        if ($uploadedFile && FileHelper::exists($uploadedFile)) {
            ResourceCleanupHelper::cleanupTempFiles($uploadedFile);
        }
    }

    /**
     * Очистка директории при ошибке
     *
     * @param string $dirPath Путь к директории
     * @return void
     */
    public function cleanupOnError(string $dirPath): void
    {
        if (FileHelper::isDirectory($dirPath)) {
            ResourceCleanupHelper::deleteDirectory($dirPath);
        }
    }

    /**
     * Очистка GitHub суффиксов из имени
     *
     * @param string $name Имя
     * @return string
     */
    private function cleanGitHubSuffix(string $name): string
    {
        $suffixes = ['-main', '-master', '-dev', '-develop', '-development'];
        foreach ($suffixes as $suffix) {
            if (str_ends_with($name, $suffix)) {
                $name = substr($name, 0, -strlen($suffix));
                break;
            }
        }
        return $name;
    }
}
