<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateFileExecutionException;
use App\Exceptions\SupportCenter\HostSystemUpdateRecoveryException;
use App\Models\HostSystemUpdateExecution;

/**
 * vendor 原子切换快照；旧 vendor 保留到整个系统更新提交成功。
 */
final class HostSystemUpdateVendorSnapshotService
{
    public function backupDirectory(HostSystemUpdateExecution $execution): string
    {
        return base_path('vendor.__dberp_backup_'.HostSystemUpdateWorkspaceStorage::sanitizeExecutionNo(
            (string) $execution->execution_no,
        ));
    }

    public function failedDirectory(HostSystemUpdateExecution $execution): string
    {
        return base_path('vendor.__dberp_failed_'.HostSystemUpdateWorkspaceStorage::sanitizeExecutionNo(
            (string) $execution->execution_no,
        ));
    }

    public function switchToExtractedVendor(
        HostSystemUpdateExecution $execution,
        string $extractDirectory,
    ): void {
        $vendorDirectory = base_path('vendor');
        $backupDirectory = $this->backupDirectory($execution);

        // 上一请求可能已完成目录切换但尚未来得及写进度，允许幂等续跑。
        if (is_dir($backupDirectory) && is_dir($vendorDirectory)) {
            if (! is_file($vendorDirectory.DIRECTORY_SEPARATOR.'autoload.php')) {
                throw HostSystemUpdateFileExecutionException::vendorCopyFailed('vendor/autoload.php', true);
            }

            return;
        }

        if (is_dir($vendorDirectory) && ! @rename($vendorDirectory, $backupDirectory)) {
            throw HostSystemUpdateFileExecutionException::vendorCopyFailed('vendor', true);
        }

        if (! @rename($extractDirectory, $vendorDirectory)) {
            if (is_dir($backupDirectory) && ! is_dir($vendorDirectory)) {
                @rename($backupDirectory, $vendorDirectory);
            }

            throw HostSystemUpdateFileExecutionException::vendorCopyFailed('vendor', true);
        }
    }

    public function restore(HostSystemUpdateExecution $execution): bool
    {
        $vendorDirectory = base_path('vendor');
        $backupDirectory = $this->backupDirectory($execution);

        if (! is_dir($backupDirectory)) {
            return false;
        }

        $failedDirectory = $this->failedDirectory($execution);
        HostSystemUpdateWorkspaceStorage::deleteDirectory($failedDirectory);

        if (is_dir($vendorDirectory) && ! @rename($vendorDirectory, $failedDirectory)) {
            throw HostSystemUpdateRecoveryException::fileRestoreFailed('vendor_snapshot_move_current_failed');
        }

        if (! @rename($backupDirectory, $vendorDirectory)) {
            if (is_dir($failedDirectory) && ! is_dir($vendorDirectory)) {
                @rename($failedDirectory, $vendorDirectory);
            }

            throw HostSystemUpdateRecoveryException::fileRestoreFailed('vendor_snapshot_restore_failed');
        }

        HostSystemUpdateWorkspaceStorage::deleteDirectory($failedDirectory);

        return true;
    }

    public function commit(HostSystemUpdateExecution $execution): void
    {
        HostSystemUpdateWorkspaceStorage::deleteDirectory($this->backupDirectory($execution));
        HostSystemUpdateWorkspaceStorage::deleteDirectory($this->failedDirectory($execution));
    }

    public function hasRestorableSnapshot(HostSystemUpdateExecution $execution): bool
    {
        return is_dir($this->backupDirectory($execution));
    }
}
