<?php

declare(strict_types=1);

namespace App\Services\Install;

use Illuminate\Support\Facades\Artisan;
use Throwable;

/**
 * Web 安装可选收尾：幂等执行 storage:link，失败仅记警告不阻断已安装状态。
 */
final class WebInstallOptionalStorageLinkStep
{
    public function __construct(
        private readonly InstallPublicStorageLinkGuard $publicStorageLinkGuard,
        private readonly DBErpInstallLogger $installLogger,
    ) {}

    public function run(WebInstallFinalizationWarnings $warnings): void
    {
        $link = $this->publicStorageLinkGuard->publicStoragePath();
        $target = $this->publicStorageLinkGuard->publicStorageTargetPath();

        if ($this->publicStorageLinkGuard->isValidLink($link, $target)) {
            $this->installLogger->optionalCommandOutcome(
                'storage:link',
                'skipped',
                0,
                'public/storage 已是有效链接，跳过 storage:link',
                [
                    'reason' => 'valid_link_exists',
                    'warning_message' => null,
                ],
            );

            return;
        }

        if (file_exists($link) || is_link($link)) {
            $warning = WebInstallUserMessages::STORAGE_LINK_INVALID_EXISTING_PATH;
            $warnings->add($warning);
            $this->installLogger->optionalCommandOutcome(
                'storage:link',
                'warn_invalid_path',
                null,
                'public/storage 存在但不是指向 storage/app/public 的有效链接',
                [
                    'reason' => 'invalid_existing_path',
                    'link_path' => $link,
                    'target_path' => $target,
                    'warning_message' => $warning,
                ],
            );

            return;
        }

        if (! $this->publicStorageLinkGuard->isPublicDirectoryWritable()) {
            $warning = WebInstallUserMessages::STORAGE_LINK_PUBLIC_NOT_WRITABLE;
            $warnings->add($warning);
            $this->installLogger->optionalCommandOutcome(
                'storage:link',
                'skipped',
                null,
                'public 目录不可写，跳过 storage:link',
                [
                    'reason' => 'public_not_writable',
                    'link_path' => $link,
                    'warning_message' => $warning,
                ],
            );

            return;
        }

        $this->runArtisanStorageLink($warnings);
    }

    private function runArtisanStorageLink(WebInstallFinalizationWarnings $warnings): void
    {
        $this->installLogger->stepProgress('artisan', '开始执行可选收尾：php artisan storage:link', [
            'command' => 'storage:link',
            'optional' => true,
        ]);

        try {
            $exitCode = Artisan::call('storage:link', []);
            $output = Artisan::output();
            $this->installLogger->commandFinished('storage:link', 'Artisan storage:link 已结束（可选收尾）', $exitCode, $output);

            if ($exitCode === 0) {
                $this->installLogger->optionalCommandOutcome(
                    'storage:link',
                    'executed',
                    $exitCode,
                    $output,
                    ['reason' => 'artisan_executed', 'warning_message' => null],
                );

                return;
            }

            $warning = WebInstallUserMessages::STORAGE_LINK_PERMISSION_FAILED;
            $warnings->add($warning);
            $this->installLogger->optionalCommandOutcome(
                'storage:link',
                'failed',
                $exitCode,
                $output,
                [
                    'reason' => 'artisan_non_zero_exit',
                    'warning_message' => $warning,
                    'error_output' => $output,
                ],
            );
        } catch (Throwable $e) {
            $warning = WebInstallUserMessages::STORAGE_LINK_PERMISSION_FAILED;
            $warnings->add($warning);
            $this->installLogger->technicalFailure('optional_finalizer', '可选收尾命令异常：storage:link', $e);
            $this->installLogger->optionalCommandOutcome(
                'storage:link',
                'failed',
                null,
                $e->getMessage(),
                [
                    'reason' => 'artisan_exception',
                    'warning_message' => $warning,
                    'error_output' => $e->getMessage(),
                ],
            );
        }
    }
}
