<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

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)) {
            throw HostSystemUpdateFileExecutionException::checksumInvalid($relativePath, $partialChangesApplied);
        }

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

        if ($expectedHash === null || preg_match('/^[a-f0-9]+$/i', $expectedHash) !== 1) {
            throw HostSystemUpdateFileExecutionException::checksumInvalid($relativePath, $partialChangesApplied);
        }

        $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 $this->resolveStringChecksum($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;

        if (! is_string($hash)) {
            return [$algorithm, null];
        }

        [$hashAlgorithm, $normalizedHash] = $this->resolveStringChecksum($hash);

        if (($expected['algorithm'] ?? null) === null) {
            $algorithm = $hashAlgorithm;
        }

        return [$algorithm, $normalizedHash];
    }

    /**
     * 清单校验允许 `sha256:` 前缀；替换阶段必须剥掉后再比对裸 hex。
     *
     * @return array{0: string, 1: string|null}
     */
    private function resolveStringChecksum(string $expected): array
    {
        $trimmed = trim($expected);

        if ($trimmed === '') {
            return [self::DEFAULT_ALGORITHM, null];
        }

        $supported = implode('|', self::SUPPORTED_ALGORITHMS);

        if (preg_match('/^('.$supported.'):(.+)$/i', $trimmed, $matches) === 1) {
            $hash = trim($matches[2]);

            return [strtolower($matches[1]), $hash !== '' ? $hash : null];
        }

        return [self::DEFAULT_ALGORITHM, $trimmed];
    }
}
