<?php

declare(strict_types=1);

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

namespace App\Services\Plugin;

use App\Support\DberpProductCode;

/**
 * 校验 plugin.json 必填与可选字段；错误码供日志使用，不直接暴露给业务用户。
 */
final class HostPluginManifestValidator
{
    public const PRODUCT_DBERP = DberpProductCode::SUPPORT_CENTER;

    private const ALIAS_KEBAB_PATTERN = '/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/';

    private const PERMISSION_CODE_PATTERN = '/^plugin\.[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/';

    /**
     * @return array{valid: bool, manifest: array<string, mixed>|null, error_code: ?string}
     */
    public function validateFile(string $manifestPath): array
    {
        if (! is_file($manifestPath)) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_missing',
            ];
        }

        $raw = @file_get_contents($manifestPath);

        if ($raw === false || trim($raw) === '') {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_empty',
            ];
        }

        try {
            $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        } catch (\JsonException) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_invalid_json',
            ];
        }

        if (! is_array($decoded)) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_not_object',
            ];
        }

        return $this->validateManifest($decoded);
    }

    /**
     * 官方 Support Center 安装/更新包：在基线校验之上执行 DBErp 官方字段规则。
     *
     * @return array{valid: bool, manifest: array<string, mixed>|null, error_code: ?string}
     */
    public function validateOfficialFile(string $manifestPath): array
    {
        $base = $this->validateFile($manifestPath);

        if (! $base['valid'] || ! is_array($base['manifest'])) {
            return $base;
        }

        return $this->validateOfficialManifest($base['manifest']);
    }

    /**
     * @param  array<string, mixed>  $manifest
     * @return array{valid: bool, manifest: array<string, mixed>|null, error_code: ?string}
     */
    public function validateManifest(array $manifest): array
    {
        foreach (['name', 'alias', 'version'] as $requiredField) {
            $value = $manifest[$requiredField] ?? null;

            if (! is_string($value) || trim($value) === '') {
                return [
                    'valid' => false,
                    'manifest' => null,
                    'error_code' => 'manifest_required_'.$requiredField.'_missing',
                ];
            }
        }

        $alias = trim((string) $manifest['alias']);

        if (! $this->isKebabCaseAlias($alias)) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_alias_invalid',
            ];
        }

        if (isset($manifest['providers']) && ! $this->isStringList($manifest['providers'])) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_providers_invalid',
            ];
        }

        if (isset($manifest['dependencies']) && ! is_array($manifest['dependencies'])) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_dependencies_invalid',
            ];
        }

        foreach (['entry_class', 'config_route', 'display_name', 'description', 'vendor', 'type', 'product'] as $optionalStringField) {
            if (! isset($manifest[$optionalStringField])) {
                continue;
            }

            if (! is_string($manifest[$optionalStringField]) || trim((string) $manifest[$optionalStringField]) === '') {
                return [
                    'valid' => false,
                    'manifest' => null,
                    'error_code' => 'manifest_'.$optionalStringField.'_invalid',
                ];
            }
        }

        if (isset($manifest['permissions']) && ! $this->isPermissionList($manifest['permissions'], $alias)) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_permissions_invalid',
            ];
        }

        if (isset($manifest['navigation']) && ! is_array($manifest['navigation'])) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_navigation_invalid',
            ];
        }

        if (isset($manifest['requires']) && ! is_array($manifest['requires'])) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_requires_invalid',
            ];
        }

        return [
            'valid' => true,
            'manifest' => $manifest,
            'error_code' => null,
        ];
    }

    /**
     * @param  array<string, mixed>  $manifest
     * @return array{valid: bool, manifest: array<string, mixed>|null, error_code: ?string}
     */
    public function validateOfficialManifest(array $manifest): array
    {
        $base = $this->validateManifest($manifest);

        if (! $base['valid'] || ! is_array($base['manifest'])) {
            return $base;
        }

        /** @var array<string, mixed> $validated */
        $validated = $base['manifest'];
        $alias = trim((string) $validated['alias']);

        $product = trim((string) ($validated['product'] ?? ''));

        if ($product !== self::PRODUCT_DBERP) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_product_invalid',
            ];
        }

        if (! isset($validated['support_center']) || ! is_array($validated['support_center'])) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_support_center_missing',
            ];
        }

        /** @var array<string, mixed> $supportCenter */
        $supportCenter = $validated['support_center'];

        $supportProductCode = trim((string) ($supportCenter['product_code'] ?? ''));

        if ($supportProductCode !== self::PRODUCT_DBERP) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_support_center_product_code_invalid',
            ];
        }

        $pluginCode = trim((string) ($supportCenter['plugin_code'] ?? ''));

        if ($pluginCode === '' || ! $this->isKebabCaseAlias($pluginCode)) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_support_center_plugin_code_invalid',
            ];
        }

        if ($pluginCode !== $alias) {
            return [
                'valid' => false,
                'manifest' => null,
                'error_code' => 'manifest_support_center_plugin_code_mismatch',
            ];
        }

        return [
            'valid' => true,
            'manifest' => $validated,
            'error_code' => null,
        ];
    }

    /**
     * @param  array<string, mixed>  $manifest
     */
    public function assertAliasMatchesCode(array $manifest, string $expectedPluginCode): bool
    {
        $alias = trim((string) ($manifest['alias'] ?? ''));
        $identifier = trim((string) ($manifest['identifier'] ?? $alias));
        $expected = trim($expectedPluginCode);

        if ($alias === $expected || $identifier === $expected) {
            return true;
        }

        /** @var array<string, mixed>|null $supportCenter */
        $supportCenter = is_array($manifest['support_center'] ?? null) ? $manifest['support_center'] : null;
        $pluginCode = trim((string) ($supportCenter['plugin_code'] ?? ''));

        return $pluginCode !== '' && $pluginCode === $expected;
    }

    public function aliasToPermissionSegment(string $alias): string
    {
        return str_replace('-', '_', trim($alias));
    }

    private function isKebabCaseAlias(string $alias): bool
    {
        return $alias !== '' && preg_match(self::ALIAS_KEBAB_PATTERN, $alias) === 1;
    }

    private function isStringList(mixed $value): bool
    {
        if (! is_array($value)) {
            return false;
        }

        foreach ($value as $item) {
            if (! is_string($item) || trim($item) === '') {
                return false;
            }
        }

        return true;
    }

    private function isPermissionList(mixed $value, string $alias): bool
    {
        if (! is_array($value)) {
            return false;
        }

        $expectedSegment = $this->aliasToPermissionSegment($alias);

        foreach ($value as $permission) {
            if (! is_string($permission) || trim($permission) === '') {
                return false;
            }

            if (preg_match(self::PERMISSION_CODE_PATTERN, $permission) !== 1) {
                return false;
            }

            $segments = explode('.', $permission);

            if (($segments[1] ?? '') !== $expectedSegment) {
                return false;
            }
        }

        return true;
    }
}
