<?php

declare(strict_types=1);

namespace EasyChat\Library\Update;

use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\SafeRuntime;

final class UpdateCenterService
{
    private $registry;
    private SignedUpdateCatalog $catalog;

    public function __construct($registry, ?SignedUpdateCatalog $catalog = null)
    {
        $this->registry = $registry;
        $this->catalog = $catalog ?: new SignedUpdateCatalog($registry);
    }

     
    public function snapshot(bool $forceRefresh = false): array
    {
        $current = defined('EASYCHAT_VERSION') ? (string)EASYCHAT_VERSION : '1.0.0';
        $profile = UpdatePlatformProfile::current();
        $catalog = $this->catalog->latest($forceRefresh);
        if (empty($catalog['success'])) {
            return [
                'success' => false,
                'error' => (string)($catalog['error'] ?? 'update_catalog_unavailable'),
                'current_version' => $current,
                'platform' => $profile,
            ];
        }

        $payload = $catalog['payload'];
        $latest = (string)$payload['version'];
        $hasUpdate = version_compare($latest, $current, '>');
        $minimumUpdaterVersion = (string)$payload['minimum_updater_version'];
        $updaterCompatible = version_compare($current, $minimumUpdaterVersion, '>=');
        $artifact = $this->primaryArtifact($profile, $payload);
        $checks = $this->preflight($profile, $payload, $artifact, $updaterCompatible);
        $ready = true;
        foreach ($checks as $check) {
            if (empty($check['ok']) && !empty($check['blocking'])) {
                $ready = false;
                break;
            }
        }

        return [
            'success' => true,
            'current_version' => $current,
            'latest_version' => $latest,
            'has_update' => $hasUpdate,
            'platform' => $profile,
            'artifact' => $artifact,
            'artifacts' => $this->allowedArtifacts($profile, $payload),
            'minimum_updater_version' => $minimumUpdaterVersion,
            'updater_compatible' => $updaterCompatible,
            'updater_transition_required' => $hasUpdate
                && !$updaterCompatible
                && ($profile['management_mode'] ?? '') === 'managed_runtime'
                && is_array($artifact)
                && ($artifact['key'] ?? '') === 'generic',
            'release_notes_url' => (string)$payload['release_notes_url'],
            'published_at' => (string)$payload['published_at'],
            'verified' => true,
            'from_cache' => !empty($catalog['from_cache']),
            'stale' => !empty($catalog['stale']),
            'ready' => $ready,
            'checks' => $checks,
        ];
    }

     
    public function installGeneric(): array
    {
        $snapshot = $this->snapshot(true);
        if (empty($snapshot['success']) || empty($snapshot['has_update']) || empty($snapshot['ready'])) {
            return [
                'success' => false,
                'error' => !empty($snapshot['success']) ? 'update_not_ready' : (string)($snapshot['error'] ?? 'update_catalog_unavailable'),
            ];
        }
        if (($snapshot['platform']['management_mode'] ?? '') !== 'managed_runtime'
            || !is_array($snapshot['artifact'] ?? null)
            || ($snapshot['artifact']['key'] ?? '') !== 'generic'
        ) {
            return ['success' => false, 'error' => 'update_requires_native_cms'];
        }

        return (new GenericUpdateInstaller($this->registry))->install(
            $snapshot['artifact'],
            (string)$snapshot['latest_version']
        );
    }

     
    public function prepareNativePackage(string $artifactKey): array
    {
        $snapshot = $this->snapshot(true);
        if (empty($snapshot['success']) || empty($snapshot['has_update'])) {
            throw new \RuntimeException('update_not_available');
        }
        if (empty($snapshot['ready'])) {
            throw new \RuntimeException('update_not_ready');
        }
        $profile = $snapshot['platform'];
        if (($profile['management_mode'] ?? '') === 'managed_runtime'
            || !UpdatePlatformProfile::allowsArtifact($profile, $artifactKey)
        ) {
            throw new \RuntimeException('update_artifact_not_allowed');
        }
        $artifact = $snapshot['artifacts'][$artifactKey] ?? null;
        if (!is_array($artifact)) {
            throw new \RuntimeException('update_artifact_unavailable');
        }

        $work = rtrim(sys_get_temp_dir(), '/\\') . '/easychat-native-update-' . bin2hex(random_bytes(16));
        if (!mkdir($work, 0700) || !chmod($work, 0700)) {
            throw new \RuntimeException('update_work_directory_unavailable');
        }
        try {
            $path = (new VerifiedArtifactDownloader(new ExternalHttpClient($this->registry)))->download($artifact, $work);
        } catch (\Throwable $e) {
            SafeRuntime::rmdir($work);
            throw $e;
        }

        return [
            'path' => $path,
            'filename' => (string)$artifact['filename'],
            'size_bytes' => (int)$artifact['size_bytes'],
            'sha256' => (string)$artifact['sha256'],
            'work_dir' => $work,
        ];
    }

     
    private function allowedArtifacts(array $profile, array $payload): array
    {
        $result = [];
        foreach ($profile['allowed_artifact_keys'] ?? [] as $key) {
            if (isset($payload['artifacts_by_key'][$key])) {
                $result[$key] = $payload['artifacts_by_key'][$key];
            }
        }
        return $result;
    }

     
    private function primaryArtifact(array $profile, array $payload): ?array
    {
        $key = (string)($profile['primary_artifact_key'] ?? '');
        return $key !== '' && isset($payload['artifacts_by_key'][$key])
            ? $payload['artifacts_by_key'][$key]
            : null;
    }

     
    private function preflight(array $profile, array $payload, ?array $artifact, bool $updaterCompatible): array
    {
        $checks = [
            $this->check('signature', 'Подпись релиза', true, true, 'Архив и метаданные подтверждены ключом EasyChat.'),
            $this->check(
                'updater_version',
                'Совместимость обновлятора',
                $updaterCompatible,
                true,
                'Требуется EasyChat ' . (string)$payload['minimum_updater_version'] . ' или новее.'
            ),
        ];
        if (($profile['management_mode'] ?? '') === 'cms_store_native') {
            $checks[] = $this->check(
                'cms_store_native',
                'Штатное обновление платформы',
                true,
                true,
                'Новая версия устанавливается через ' . $profile['native_label'] . '; EasyChat не обходит правила магазина приложений.'
            );
            return $checks;
        }
        if (($profile['management_mode'] ?? '') !== 'managed_runtime') {
            $checks[] = $this->check('cms_native', 'Штатный установщик CMS', true, true, 'EasyChat передаст проверенный архив в ' . $profile['native_label'] . '.');
            return $checks;
        }

        $checks[] = $this->check('openssl', 'Проверка подписи', function_exists('openssl_verify'), true, 'На сервере требуется расширение OpenSSL.');
        $checks[] = $this->check('curl', 'Защищенная загрузка', function_exists('curl_init'), true, 'На сервере требуется расширение cURL.');
        $checks[] = $this->check('zip', 'Распаковка архива', class_exists('ZipArchive'), true, 'На сервере требуется расширение ZIP.');
        $checks[] = $this->check('runtime_write', 'Доступ к файлам EasyChat', is_dir(SITE_PATH) && is_writable(SITE_PATH) && !is_link(SITE_PATH), true, 'PHP должен иметь право обновлять папку EasyChat.');
        $free = @disk_free_space(SITE_PATH);
        $required = is_array($artifact) ? max(64 * 1024 * 1024, (int)$artifact['size_bytes'] * 5) : 64 * 1024 * 1024;
        $checks[] = $this->check('disk', 'Свободное место для rollback', is_numeric($free) && (float)$free >= $required, true, 'Нужно не менее ' . $this->formatBytes($required) . ' свободного места.');
        return $checks;
    }

     
    private function check(string $key, string $label, bool $ok, bool $blocking, string $detail): array
    {
        return compact('key', 'label', 'ok', 'blocking', 'detail');
    }

    private function formatBytes(int $bytes): string
    {
        return number_format($bytes / 1048576, 0, ',', ' ') . ' МБ';
    }
}
