<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdatePostApplyException;
use App\Models\HostSystemUpdateExecution;
use Composer\InstalledVersions;

/** 验证磁盘上的新代码、Composer 依赖与当前 PHP 运行时已经对齐。 */
final class HostSystemUpdateRuntimeReadinessService
{
    public function __construct(
        private readonly HostSystemUpdateExecutionContextResolver $contextResolver,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
    ) {}

    /** @return array<string, mixed> */
    public function check(HostSystemUpdateExecution $execution): array
    {
        if (! is_file(base_path('vendor/autoload.php'))) {
            throw HostSystemUpdatePostApplyException::runtimeReadinessFailed('vendor_autoload_missing');
        }

        $manifest = $this->contextResolver->manifest($execution);
        $runtime = $manifest[HostSystemUpdateManifestContract::RUNTIME] ?? [];
        $runtime = is_array($runtime) ? $runtime : [];

        $expectedLockHash = $runtime['composer_lock_sha256'] ?? null;

        if (is_string($expectedLockHash) && $expectedLockHash !== '') {
            $actual = is_file(base_path('composer.lock')) ? hash_file('sha256', base_path('composer.lock')) : false;

            if (! is_string($actual) || ! hash_equals(strtolower($expectedLockHash), strtolower($actual))) {
                throw HostSystemUpdatePostApplyException::runtimeReadinessFailed('composer_lock_mismatch');
            }
        }

        $packages = $runtime['required_packages'] ?? [];

        if (is_array($packages)) {
            foreach ($packages as $package => $version) {
                if (! is_string($package) || ! InstalledVersions::isInstalled($package)) {
                    throw HostSystemUpdatePostApplyException::runtimeReadinessFailed('package_missing:'.(string) $package);
                }

                if (is_string($version) && $version !== '') {
                    $installed = InstalledVersions::getPrettyVersion($package) ?? InstalledVersions::getVersion($package);

                    if (! is_string($installed) || ! $this->versionsMatch($installed, $version)) {
                        throw HostSystemUpdatePostApplyException::runtimeReadinessFailed('package_version_mismatch:'.$package);
                    }
                }
            }
        }

        $classes = $runtime['required_classes'] ?? [];

        if (is_array($classes)) {
            foreach ($classes as $class) {
                if (! is_string($class) || $class === '' || ! class_exists($class)) {
                    throw HostSystemUpdatePostApplyException::runtimeReadinessFailed('class_missing:'.(string) $class);
                }
            }
        }

        $summary = [
            'required_package_count' => is_array($packages) ? count($packages) : 0,
            'required_class_count' => is_array($classes) ? count($classes) : 0,
            'composer_lock_verified' => is_string($expectedLockHash) && $expectedLockHash !== '',
        ];

        $this->executionLogger->logStepCompleted(
            $execution,
            'runtime_readiness',
            (string) __('dberp.system_update.post_apply.log.runtime_readiness_completed'),
            $summary,
            now()->utc(),
        );

        return $summary;
    }

    private function versionsMatch(string $installed, string $required): bool
    {
        $installed = ltrim($installed, 'vV');
        $required = ltrim($required, 'vV');

        return $installed === $required || str_starts_with($installed, $required.'-');
    }
}
