<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use RuntimeException;

/**
 * 为后续完整版本目录部署提供 current 软链接原子切换能力。
 *
 * 该能力默认关闭，且只接受 releases_root 内已完成自检的完整版本目录。
 */
final class HostSystemUpdateAtomicReleaseSwitcher
{
    public function activate(string $releaseDirectory): void
    {
        if (! (bool) config('dberp.system_update.atomic_release.enabled', false)) {
            throw new RuntimeException('atomic_release_switch_disabled');
        }

        if (PHP_OS_FAMILY === 'Windows') {
            throw new RuntimeException('atomic_release_switch_requires_posix');
        }

        $release = realpath($releaseDirectory);
        $releasesRoot = realpath((string) config('dberp.system_update.atomic_release.releases_root', ''));
        $currentLink = (string) config('dberp.system_update.atomic_release.current_link', '');
        $currentParent = $currentLink !== '' ? realpath(dirname($currentLink)) : false;

        if ($release === false || $releasesRoot === false || $currentParent === false) {
            throw new RuntimeException('atomic_release_path_invalid');
        }

        if (! $this->isWithin($release, $releasesRoot) || $release === $releasesRoot) {
            throw new RuntimeException('atomic_release_outside_releases_root');
        }

        if (! is_file($release.DIRECTORY_SEPARATOR.'artisan')
            || ! is_file($release.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php')) {
            throw new RuntimeException('atomic_release_not_ready');
        }

        if ((file_exists($currentLink) || is_link($currentLink)) && ! is_link($currentLink)) {
            throw new RuntimeException('atomic_release_current_is_not_link');
        }

        $temporaryLink = $currentParent.DIRECTORY_SEPARATOR.'.current.__dberp_next_'.bin2hex(random_bytes(8));

        if (! @symlink($release, $temporaryLink)) {
            throw new RuntimeException('atomic_release_temporary_link_failed');
        }

        try {
            // 同一目录内 rename 覆盖软链接在 POSIX 上是原子的，失败时旧 current 不变。
            if (! @rename($temporaryLink, $currentLink)) {
                throw new RuntimeException('atomic_release_switch_failed');
            }
        } finally {
            if (is_link($temporaryLink)) {
                @unlink($temporaryLink);
            }
        }
    }

    private function isWithin(string $path, string $root): bool
    {
        $normalizedPath = str_replace('\\', '/', $path);
        $normalizedRoot = rtrim(str_replace('\\', '/', $root), '/');

        return str_starts_with($normalizedPath.'/', $normalizedRoot.'/');
    }
}
