<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateRecoveryException;
use App\Support\DberpMaintenanceDownCommandOptions;
use Illuminate\Support\Facades\Artisan;
use Throwable;

/**
 * 在当前 PHP 进程内执行 artisan down/up（与 Laravel 维护文件机制一致，不依赖 proc_open）。
 */
final class InProcessHostSystemUpdateMaintenanceCommandHandler implements HostSystemUpdateMaintenanceCommandHandler
{
    public function __construct(
        private readonly DberpMaintenanceDownCommandOptions $maintenanceDownOptions,
    ) {}

    public function executeDown(?int $timeoutSeconds): HostSystemUpdateArtisanCommandResult
    {
        $this->maintenanceDownOptions->applyExceptPathsForDownCommand();

        try {
            return $this->invoke('down', $this->maintenanceDownOptions->artisanArguments());
        } finally {
            $this->maintenanceDownOptions->clearExceptPathsRegistration();
        }
    }

    public function executeUp(?int $timeoutSeconds): HostSystemUpdateArtisanCommandResult
    {
        return $this->invoke('up');
    }

    /**
     * @param  array<string, string|int|bool>  $arguments
     */
    private function invoke(string $command, array $arguments = []): HostSystemUpdateArtisanCommandResult
    {
        $startedAt = now()->utc();
        $startedMicro = microtime(true);

        try {
            $exitCode = Artisan::call($command, $arguments);
            $output = Artisan::output();
        } catch (Throwable) {
            throw HostSystemUpdateRecoveryException::maintenanceCommandFailed($command, 1);
        }

        $finishedAt = now()->utc();
        $durationMs = max(0, (int) round((microtime(true) - $startedMicro) * 1000));

        $result = new HostSystemUpdateArtisanCommandResult(
            command: $command,
            arguments: [],
            exitCode: $exitCode,
            stdout: HostSystemUpdateArtisanOutputSanitizer::sanitize($output),
            stderr: '',
            startedAt: $startedAt,
            finishedAt: $finishedAt,
            durationMs: $durationMs,
        );

        if (! $result->succeeded()) {
            throw HostSystemUpdateRecoveryException::maintenanceCommandFailed($command, $exitCode);
        }

        return $result;
    }
}
