<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateFileExecutionException;

/**
 * 更新包 manifest 文件级 checksum 校验（默认 sha256）。
 */
final class HostSystemUpdateFileChecksumVerifier
{
    public const DEFAULT_ALGORITHM = 'sha256';

    /**
     * @var list<string>
     */
    private const SUPPORTED_ALGORITHMS = ['sha256', 'sha384', 'sha512'];

    /**
     * @param  array<string, mixed>  $manifest
     */
    public function verifyStagedFile(
        string $relativePath,
        string $stagedFilePath,
        array $manifest,
        bool $partialChangesApplied = false,
    ): void {
        if (! is_file($stagedFilePath)) {
            throw HostSystemUpdateFileExecutionException::stagedSourceMissing($relativePath);
        }

        $checksums = $manifest[HostSystemUpdateManifestContract::CHECKSUMS] ?? [];

        if (! is_array($checksums) || ! array_key_exists($relativePath, $checksums)) {
            return;
        }

        $expected = $checksums[$relativePath];
        [$algorithm, $expectedHash] = $this->resolveExpectedChecksum($expected);

        if ($expectedHash === null || $expectedHash === '') {
            return;
        }

        $actualHash = hash_file($algorithm, $stagedFilePath);

        if (! is_string($actualHash) || ! hash_equals(strtolower($expectedHash), strtolower($actualHash))) {
            throw HostSystemUpdateFileExecutionException::checksumMismatch($relativePath, $partialChangesApplied);
        }
    }

    /**
     * @return array{0: string, 1: string|null}
     */
    private function resolveExpectedChecksum(mixed $expected): array
    {
        if (is_string($expected)) {
            return [self::DEFAULT_ALGORITHM, $expected];
        }

        if (! is_array($expected)) {
            return [self::DEFAULT_ALGORITHM, null];
        }

        $algorithm = is_string($expected['algorithm'] ?? null)
            ? strtolower($expected['algorithm'])
            : self::DEFAULT_ALGORITHM;

        if (! in_array($algorithm, self::SUPPORTED_ALGORITHMS, true)) {
            $algorithm = self::DEFAULT_ALGORITHM;
        }

        $hash = $expected['hash'] ?? $expected['checksum'] ?? $expected['value'] ?? null;

        return [$algorithm, is_string($hash) ? $hash : null];
    }
}
