<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdatePostApplyException;

/**
 * 系统更新白名单 Artisan 命令执行（禁止任意 manifest 命令）。
 */
final class HostSystemUpdateArtisanCommandRunner
{
    public const COMMAND_MIGRATE = 'migrate';

    public const COMMAND_OPTIMIZE_CLEAR = 'optimize:clear';

    public const COMMAND_CONFIG_CLEAR = 'config:clear';

    public const COMMAND_ROUTE_CLEAR = 'route:clear';

    public const COMMAND_VIEW_CLEAR = 'view:clear';

    /**
     * @var array<string, list<string>>
     */
    private const ALLOWED_COMMANDS = [
        self::COMMAND_MIGRATE => ['--force', '--no-interaction'],
        self::COMMAND_OPTIMIZE_CLEAR => [],
        self::COMMAND_CONFIG_CLEAR => [],
        self::COMMAND_ROUTE_CLEAR => [],
        self::COMMAND_VIEW_CLEAR => [],
    ];

    public function __construct(
        private readonly HostSystemUpdateArtisanCommandHandler $handler,
    ) {}

    /**
     * @param  list<string>  $arguments
     */
    public function run(string $command, array $arguments = [], ?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        $this->assertCommandAllowed($command, $arguments);

        $timeout = $timeoutSeconds ?? max(30, (int) config('dberp.system_update.artisan_timeout_seconds', 300));

        return $this->handler->execute($command, $arguments, $timeout);
    }

    public function runMigrate(?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        return $this->run(self::COMMAND_MIGRATE, ['--force', '--no-interaction'], $timeoutSeconds);
    }

    public function runOptimizeClear(?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        return $this->run(self::COMMAND_OPTIMIZE_CLEAR, [], $timeoutSeconds);
    }

    public function runConfigClear(?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        return $this->run(self::COMMAND_CONFIG_CLEAR, [], $timeoutSeconds);
    }

    public function runRouteClear(?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        return $this->run(self::COMMAND_ROUTE_CLEAR, [], $timeoutSeconds);
    }

    public function runViewClear(?int $timeoutSeconds = null): HostSystemUpdateArtisanCommandResult
    {
        return $this->run(self::COMMAND_VIEW_CLEAR, [], $timeoutSeconds);
    }

    /**
     * @param  list<string>  $arguments
     */
    private function assertCommandAllowed(string $command, array $arguments): void
    {
        if (! array_key_exists($command, self::ALLOWED_COMMANDS)) {
            throw HostSystemUpdatePostApplyException::commandNotAllowed($command);
        }

        $allowedArguments = self::ALLOWED_COMMANDS[$command];
        sort($allowedArguments);
        $normalizedArguments = array_values($arguments);
        sort($normalizedArguments);

        if ($normalizedArguments !== $allowedArguments) {
            throw HostSystemUpdatePostApplyException::commandNotAllowed($command);
        }
    }
}
