<?php

declare(strict_types=1);

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

namespace App\Support;

use App\Filament\Pages\HostSystemAuthorizationCenterPage;
use App\Filament\Pages\SiteOnboardingCenterPage;
use App\Models\CorePlugin;
use App\Models\HostPluginOperationLog;
use App\Services\DberpPermissionService;
use App\Services\Plugin\HostPluginCatalogInstallStateResolver;
use App\Services\Plugin\HostPluginCatalogService;
use App\Services\Plugin\HostPluginEntitlementGate;
use App\Services\Plugin\HostPluginFeatureEntryResolver;
use App\Services\Plugin\HostPluginManifestFileReader;
use App\Services\Plugin\HostPluginOfficialUsageGuidanceResolver;
use App\Services\Plugin\HostPluginOperationLock;
use App\Services\Plugin\HostPluginOperationLogger;
use App\Services\Plugin\HostPluginSettingsAccessResult;
use App\Services\Plugin\HostPluginSettingsRouteResolver;
use App\Services\Plugin\HostPluginStatusPresenter;
use App\Services\Plugin\HostPluginUpdateCheckResult;
use App\Services\Plugin\HostPluginUpdateCheckService;
use App\Services\Plugin\HostPluginUpdateStatusPresenter;
use App\Services\SupportCenter\HostSystemAuthorizationEntitlementPresenter;
use DBCore\Plugin\Enums\PluginStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;
use Throwable;

/**
 * 插件与扩展中心页只读展示数据。
 */
final class HostPluginCenterDisplay
{
    private ?string $catalogFetchError = null;

    /** @var list<array<string, mixed>>|null */
    private ?array $catalogRowsCache = null;

    /** @var array<string, string>|null */
    private ?array $activityPluginLabelMapCache = null;

    public function __construct(
        private readonly HostSystemAuthorizationCenterDisplay $authorizationCenterDisplay,
        private readonly HostSystemAuthorizationEntitlementPresenter $entitlementPresenter,
        private readonly HostPluginStatusPresenter $statusPresenter,
        private readonly HostPluginCatalogService $catalogService,
        private readonly HostPluginCatalogInstallStateResolver $catalogStateResolver,
        private readonly HostPluginUpdateCheckService $updateCheckService,
        private readonly HostPluginUpdateStatusPresenter $updateStatusPresenter,
        private readonly HostPluginSettingsRouteResolver $settingsRouteResolver,
        private readonly HostPluginFeatureEntryResolver $featureEntryResolver,
        private readonly HostPluginManifestFileReader $manifestFileReader,
        private readonly HostPluginOfficialUsageGuidanceResolver $usageGuidanceResolver,
        private readonly HostPluginEntitlementGate $entitlementGate,
        private readonly HostPluginOperationLock $operationLock,
        private readonly DberpPermissionService $permissionService,
    ) {}

    /** @var array<string, HostPluginUpdateCheckResult> */
    private array $updateCheckCache = [];

    public function installedPluginCount(): int
    {
        if (! $this->pluginTableReady()) {
            return 0;
        }

        return CorePlugin::query()
            ->where('status', '!=', PluginStatus::Uninstalled->value)
            ->count();
    }

    public function clearCatalogCache(): void
    {
        $this->catalogFetchError = null;
        $this->catalogRowsCache = null;
        $this->updateCheckCache = [];
    }

    public function isPluginCenterEnabled(): bool
    {
        return (bool) config('dberp.plugin.center_enabled', true);
    }

    public function shouldShowPrerequisiteAlert(): bool
    {
        return $this->prerequisiteAlertPanel() !== null;
    }

    /**
     * @return array{
     *     title: string,
     *     message: string,
     *     severity: string,
     *     actions: list<array{label: string, url: string}>
     * }|null
     */
    public function prerequisiteAlertPanel(): ?array
    {
        if ($this->entitlementGate->prerequisitesSatisfied()) {
            return null;
        }

        return [
            'title' => (string) __('dberp.plugin_center.prerequisite_alert.title'),
            'message' => $this->resolvePrerequisiteAlertMessage(),
            'severity' => $this->resolvePrerequisiteAlertSeverity(),
            'actions' => $this->resolvePrerequisiteAlertActions(),
        ];
    }

    /**
     * @return list<array{key: string, label: string, enabled: bool, icon: string, status_label: string}>
     */
    public function pluginEntitlementItems(): array
    {
        $entitlements = $this->authorizationCenterDisplay->record()->entitlements;
        $rows = $this->entitlementPresenter->primaryEntitlementsForDisplay(is_array($entitlements) ? $entitlements : null);

        $pluginKeys = ['can_install_plugin', 'can_update_plugin'];

        return array_values(array_map(
            fn (array $row): array => [
                'key' => $row['key'],
                'label' => $row['label'],
                'enabled' => $row['enabled'],
                'icon' => $this->entitlementIconFor($row['key']),
                'status_label' => $row['enabled']
                    ? (string) __('dberp.plugin_center.entitlements.status_granted')
                    : (string) __('dberp.plugin_center.entitlements.status_not_granted'),
            ],
            array_filter(
                $rows,
                static fn (array $row): bool => in_array($row['key'], $pluginKeys, true),
            ),
        ));
    }

    /**
     * @return list<array{
     *     identifier: string,
     *     name: string,
     *     version: string,
     *     status: string,
     *     status_label: string,
     *     status_color: string,
     *     installed_at_label: ?string,
     *     last_error: ?string,
     *     show_enable_action: bool,
     *     show_disable_action: bool,
     *     show_settings_action: bool,
     *     show_update_action: bool,
     *     update_status_label: string,
     *     update_status_color: string,
     *     update_guidance: ?string,
     *     latest_version: ?string,
     *     task_in_progress: bool,
     *     enable_blocked_reason: ?string,
     *     update_blocked_reason: ?string
     * }>
     */
    public function installedPluginRows(): array
    {
        if (! $this->pluginTableReady()) {
            return [];
        }

        $user = Auth::user();
        $rows = [];

        foreach (CorePlugin::query()->orderBy('identifier')->get() as $record) {
            $status = (string) ($record->status ?? PluginStatus::Installed->value);

            if ($status === PluginStatus::Uninstalled->value) {
                continue;
            }

            $identifier = (string) $record->identifier;
            $installedVersion = filled($record->version) ? (string) $record->version : null;
            $updateCheck = $this->resolveUpdateCheck($identifier, $installedVersion);
            $taskInProgress = $this->operationLock->isLocked($identifier);
            $canEnable = $user !== null && $this->permissionService->allows($user, DberpPermissionService::ENABLE_HOST_PLUGIN);
            $canDisable = $user !== null && $this->permissionService->allows($user, DberpPermissionService::DISABLE_HOST_PLUGIN);
            $canUpdate = $user !== null && $this->permissionService->allows($user, DberpPermissionService::UPDATE_HOST_PLUGIN);
            $settingsAccess = $this->settingsRouteResolver->resolveAccess($identifier, $user);
            $featureEntries = $this->featureEntryResolver->entriesFor($identifier, $user);

            /** @var array<string, mixed> $meta */
            $meta = is_array($record->meta) ? $record->meta : [];

            $rows[] = [
                'identifier' => $identifier,
                'name' => $this->resolvePluginDisplayName($record, $meta),
                'version' => $installedVersion ?? (string) __('dberp.plugin_center.installed.version_unknown'),
                'status' => $status,
                'status_label' => $this->statusPresenter->label($status),
                'status_color' => $this->statusPresenter->badgeColor($status),
                'installed_at_label' => $this->formatInstalledAt($meta),
                'last_error' => $this->resolveLastError($meta),
                'update_status_label' => $this->updateStatusPresenter->label($updateCheck->state),
                'update_status_color' => $this->updateStatusPresenter->badgeColor($updateCheck->state),
                'update_guidance' => $updateCheck->guidance,
                'latest_version' => $updateCheck->latestVersion,
                'task_in_progress' => $taskInProgress,
                'show_enable_action' => in_array($status, [PluginStatus::Installed->value, PluginStatus::Disabled->value, 'failed'], true)
                    && $canEnable
                    && ! $taskInProgress,
                'show_disable_action' => $status === PluginStatus::Enabled->value
                    && $canDisable
                    && ! $taskInProgress,
                'show_settings_action' => $settingsAccess->isAvailable(),
                'settings_url' => $settingsAccess->isAvailable() ? $settingsAccess->url : null,
                'settings_state' => $settingsAccess->state,
                'settings_hint' => $this->resolveSettingsHint($settingsAccess),
                'settings_blocked_reason' => $settingsAccess->isAvailable()
                    ? null
                    : $this->settingsBlockedReason($settingsAccess),
                'feature_entries' => $featureEntries,
                'usage_guidance' => $this->usageGuidanceResolver->resolve(
                    $identifier,
                    $user,
                    $status,
                    $featureEntries,
                ),
                'show_update_action' => $updateCheck->state === HostPluginUpdateCheckResult::STATE_UPDATE_AVAILABLE
                    && $canUpdate
                    && $this->entitlementGate->canUpdatePlugin()
                    && ! $taskInProgress,
                'enable_blocked_reason' => ! $canEnable
                    ? (string) __('dberp.plugin_center.lifecycle.permission_denied')
                    : ($taskInProgress ? (string) __('dberp.plugin_center.lifecycle.task_in_progress') : null),
                'disable_blocked_reason' => ! $canDisable
                    ? (string) __('dberp.plugin_center.lifecycle.permission_denied')
                    : ($taskInProgress ? (string) __('dberp.plugin_center.lifecycle.task_in_progress') : null),
                'update_state' => $updateCheck->state,
                'update_blocked_reason' => $this->resolveUpdateBlockedReason($updateCheck, $canUpdate),
            ];
        }

        return $rows;
    }

    private function resolveUpdateCheck(string $identifier, ?string $installedVersion): HostPluginUpdateCheckResult
    {
        if (isset($this->updateCheckCache[$identifier])) {
            return $this->updateCheckCache[$identifier];
        }

        $result = $this->updateCheckService->check($identifier, $installedVersion);
        $this->updateCheckCache[$identifier] = $result;

        return $result;
    }

    private function resolveUpdateBlockedReason(HostPluginUpdateCheckResult $check, bool $canUpdate): ?string
    {
        if (! $canUpdate) {
            return (string) __('dberp.plugin_center.update.permission_denied');
        }

        if (! $this->entitlementGate->canUpdatePlugin()) {
            return $this->entitlementGate->updateReason();
        }

        if ($check->state === HostPluginUpdateCheckResult::STATE_UPDATE_AVAILABLE) {
            return null;
        }

        return $check->guidance ?? $this->updateStatusPresenter->label($check->state);
    }

    public function hasInstalledPlugins(): bool
    {
        return $this->installedPluginRows() !== [];
    }

    public function installedPluginsEmptyMessage(): string
    {
        return (string) __('dberp.plugin_center.installed.empty');
    }

    public function catalogFetchErrorMessage(): ?string
    {
        $this->resolveCatalogRows();

        return $this->catalogFetchError;
    }

    /**
     * @return list<array{
     *     plugin_code: string,
     *     plugin_name: string,
     *     latest_version: ?string,
     *     description: ?string,
     *     state: string,
     *     state_label: string,
     *     state_color: string,
     *     show_install_action: bool,
     *     install_blocked_reason: ?string,
     *     installed_version: ?string
     * }>
     */
    public function catalogRows(): array
    {
        return $this->resolveCatalogRows();
    }

    public function hasCatalogRows(): bool
    {
        return $this->catalogRows() !== [];
    }

    public function catalogEmptyMessage(): string
    {
        if ($this->catalogFetchErrorMessage() !== null) {
            return $this->catalogFetchErrorMessage();
        }

        return (string) __('dberp.plugin_center.catalog.empty');
    }

    /**
     * @return list<array{
     *     time_label: string,
     *     plugin_code: ?string,
     *     plugin_label: ?string,
     *     operation_label: string,
     *     result_label: string,
     *     result_color: string,
     *     message: ?string
     * }>
     */
    public function activityRows(int $limit = 20): array
    {
        if (! $this->operationLogTableReady()) {
            return [];
        }

        $pluginLabelMap = $this->activityPluginLabelMap();
        $rows = [];

        foreach ($this->resolveActivityLogs($limit) as $log) {
            $pluginCode = filled($log->plugin_code) ? (string) $log->plugin_code : null;

            $rows[] = [
                'time_label' => $log->created_at?->timezone(DberpSystemProfileSettings::displayTimezone())->format('Y-m-d H:i:s') ?? '',
                'plugin_code' => $pluginCode,
                'plugin_label' => $this->resolveActivityPluginLabel($pluginCode, $pluginLabelMap),
                'operation_label' => $this->operationLabel((string) $log->operation),
                'operation' => (string) $log->operation,
                'result_label' => $this->resultLabel((string) $log->result_status),
                'result' => (string) $log->result_status,
                'result_color' => $this->resultColor((string) $log->result_status),
                'message' => filled($log->message) ? (string) $log->message : null,
            ];
        }

        return $rows;
    }

    /**
     * @return Builder<HostPluginOperationLog>
     */
    public function activityQuery(): Builder
    {
        $visibleCompletedOperations = HostPluginOperationLogger::displayVisibleCompletedOperations();
        $snapshotOperations = HostPluginOperationLogger::displayLatestSnapshotOperations();
        $snapshotIds = collect($snapshotOperations)
            ->map(fn (string $operation): ?int => HostPluginOperationLog::query()
                ->where('operation', $operation)
                ->whereIn('result_status', ['completed', 'failed'])
                ->orderByDesc('created_at')
                ->orderByDesc('id')
                ->value('id'))
            ->filter()
            ->values()
            ->all();

        return HostPluginOperationLog::query()
            ->where(function (Builder $query) use ($snapshotIds, $snapshotOperations, $visibleCompletedOperations): void {
                $query->where(function (Builder $query) use ($snapshotOperations, $visibleCompletedOperations): void {
                    $query->whereNotIn('operation', $snapshotOperations)
                        ->whereIn('result_status', ['completed', 'failed'])
                        ->where(function (Builder $query) use ($visibleCompletedOperations): void {
                            $query->where('result_status', 'failed')
                                ->orWhereIn('operation', $visibleCompletedOperations);
                        });
                });

                if ($snapshotIds !== []) {
                    $query->orWhereIn('id', $snapshotIds);
                }
            });
    }

    public function activityCount(): int
    {
        if (! $this->operationLogTableReady()) {
            return 0;
        }

        return $this->activityQuery()->count();
    }

    public function hasFailedActivity(): bool
    {
        if (! $this->operationLogTableReady()) {
            return false;
        }

        return $this->activityQuery()
            ->where('result_status', 'failed')
            ->exists();
    }

    public function activityPluginLabel(?string $pluginCode): ?string
    {
        return $this->resolveActivityPluginLabel($pluginCode, $this->activityPluginLabelMap());
    }

    public function activityOperationLabel(string $operation): string
    {
        return $this->operationLabel($operation);
    }

    public function activityResultLabel(string $resultStatus): string
    {
        return $this->resultLabel($resultStatus);
    }

    public function activityResultColor(string $resultStatus): string
    {
        return $this->resultColor($resultStatus);
    }

    /**
     * @return array<string, string>
     */
    public function activityOperationOptions(): array
    {
        if (! $this->operationLogTableReady()) {
            return [];
        }

        return $this->activityQuery()
            ->select('operation')
            ->distinct()
            ->orderBy('operation')
            ->pluck('operation')
            ->mapWithKeys(fn (string $operation): array => [
                $operation => $this->activityOperationLabel($operation),
            ])
            ->all();
    }

    /**
     * @return array<string, string>
     */
    public function activityPluginOptions(): array
    {
        if (! $this->operationLogTableReady()) {
            return [];
        }

        return $this->activityQuery()
            ->whereNotNull('plugin_code')
            ->select('plugin_code')
            ->distinct()
            ->orderBy('plugin_code')
            ->pluck('plugin_code')
            ->mapWithKeys(fn (string $pluginCode): array => [
                $pluginCode => $this->activityPluginLabel($pluginCode) ?? $pluginCode,
            ])
            ->all();
    }

    /**
     * @return Collection<int, HostPluginOperationLog>
     */
    private function resolveActivityLogs(int $limit): Collection
    {
        return $this->activityQuery()
            ->orderByDesc('created_at')
            ->orderByDesc('id')
            ->limit($limit)
            ->get();
    }

    public function hasActivityRows(): bool
    {
        return $this->activityRows() !== [];
    }

    public function activityEmptyMessage(): string
    {
        return (string) __('dberp.plugin_center.activity.empty');
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function resolveCatalogRows(): array
    {
        if ($this->catalogRowsCache !== null) {
            return $this->catalogRowsCache;
        }

        $result = $this->catalogService->fetchCatalog();
        $this->catalogFetchError = $result['error_message'];
        $user = Auth::user();
        $rows = [];

        foreach ($result['items'] as $item) {
            $state = $this->catalogStateResolver->resolve($item, $user);

            $rows[] = [
                'plugin_code' => $item->pluginCode,
                'plugin_name' => $item->pluginName,
                'latest_version' => $item->latestVersion,
                'description' => $item->description,
                'state' => $state['state'],
                'state_label' => $state['state_label'],
                'state_color' => $state['state_color'],
                'show_install_action' => $state['show_install_action'],
                'install_blocked_reason' => $state['install_blocked_reason'],
                'installed_version' => $state['installed_version'],
            ];
        }

        $this->catalogRowsCache = $rows;

        return $rows;
    }

    /**
     * @return array<string, string>
     */
    private function activityPluginLabelMap(): array
    {
        if ($this->activityPluginLabelMapCache !== null) {
            return $this->activityPluginLabelMapCache;
        }

        $map = [];

        if ($this->pluginTableReady()) {
            foreach (CorePlugin::query()->get(['identifier', 'name', 'meta']) as $record) {
                $identifier = trim((string) $record->identifier);

                if ($identifier === '') {
                    continue;
                }

                /** @var array<string, mixed> $meta */
                $meta = is_array($record->meta) ? $record->meta : [];
                $map[$identifier] = $this->resolvePluginDisplayName($record, $meta);
            }
        }

        $this->activityPluginLabelMapCache = $map;

        return $this->activityPluginLabelMapCache;
    }

    /**
     * @param  array<string, string>  $labelMap
     */
    private function resolveActivityPluginLabel(?string $pluginCode, array $labelMap): ?string
    {
        $code = trim((string) ($pluginCode ?? ''));

        if ($code === '') {
            return null;
        }

        if (isset($labelMap[$code])) {
            return $labelMap[$code];
        }

        $manifest = $this->manifestFileReader->enriched($code);

        if (is_array($manifest)) {
            $name = trim((string) ($manifest['display_name'] ?? $manifest['name'] ?? ''));

            if ($name !== '') {
                return $name;
            }
        }

        return $code;
    }

    private function operationLabel(string $operation): string
    {
        $key = 'dberp.plugin_center.activity.operations.'.$operation;

        return __($key) !== $key ? (string) __($key) : $operation;
    }

    private function resultLabel(string $resultStatus): string
    {
        $key = 'dberp.plugin_center.activity.results.'.$resultStatus;

        return __($key) !== $key ? (string) __($key) : $resultStatus;
    }

    private function resultColor(string $resultStatus): string
    {
        return match ($resultStatus) {
            'completed' => 'success',
            'failed' => 'danger',
            default => 'gray',
        };
    }

    private function operationLogTableReady(): bool
    {
        try {
            return Schema::hasTable('host_plugin_operation_logs');
        } catch (Throwable) {
            return false;
        }
    }

    private function pluginTableReady(): bool
    {
        try {
            return Schema::hasTable('core_plugins');
        } catch (Throwable) {
            return false;
        }
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    private function formatInstalledAt(array $meta): ?string
    {
        $raw = $meta['installed_at'] ?? null;

        if (! is_string($raw) || trim($raw) === '') {
            return null;
        }

        try {
            return Carbon::parse($raw)
                ->timezone(DberpSystemProfileSettings::displayTimezone())
                ->format('Y-m-d H:i');
        } catch (Throwable) {
            return null;
        }
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    private function resolveLastError(array $meta): ?string
    {
        foreach ([$meta['last_error'] ?? null, is_array($meta['extra'] ?? null) ? ($meta['extra']['last_error'] ?? null) : null] as $candidate) {
            if (is_string($candidate) && trim($candidate) !== '') {
                return trim($candidate);
            }
        }

        return null;
    }

    private function entitlementIconFor(string $key): string
    {
        return match ($key) {
            'can_install_plugin' => 'heroicon-o-puzzle-piece',
            'can_update_plugin' => 'heroicon-o-arrow-path',
            default => 'heroicon-o-check-badge',
        };
    }

    private function resolvePrerequisiteAlertMessage(): string
    {
        if (! $this->entitlementGate->commercialBaselineMet()) {
            return $this->entitlementGate->installReason();
        }

        if (! $this->entitlementGate->canInstallPlugin() && ! $this->entitlementGate->canUpdatePlugin()) {
            return (string) __('dberp.plugin_center.prerequisite_alert.entitlements_incomplete');
        }

        if (! $this->entitlementGate->canInstallPlugin()) {
            return $this->entitlementGate->installReason();
        }

        return $this->entitlementGate->updateReason();
    }

    private function resolvePrerequisiteAlertSeverity(): string
    {
        if (! $this->entitlementGate->commercialBaselineMet()) {
            return $this->entitlementGate->isServiceExpired() ? 'danger' : 'warning';
        }

        return 'warning';
    }

    /**
     * @return list<array{label: string, url: string}>
     */
    /**
     * @param  array<string, mixed>  $meta
     */
    private function resolvePluginDisplayName(CorePlugin $record, array $meta): string
    {
        $displayName = trim((string) ($meta['display_name'] ?? ''));

        if ($displayName !== '') {
            return $displayName;
        }

        /** @var array<string, mixed> $extra */
        $extra = is_array($meta['extra'] ?? null) ? $meta['extra'] : [];
        $displayName = trim((string) ($extra['display_name'] ?? ''));

        if ($displayName !== '') {
            return $displayName;
        }

        $name = trim((string) ($record->name ?? ''));

        if ($name !== '') {
            return $name;
        }

        return (string) $record->identifier;
    }

    private function resolveSettingsHint(HostPluginSettingsAccessResult $access): ?string
    {
        if ($access->isAvailable()) {
            return null;
        }

        if ($access->state === HostPluginSettingsAccessResult::STATE_PERMISSION_DENIED) {
            return (string) __('dberp.plugin_center.lifecycle.permission_denied');
        }

        if ($access->state === HostPluginSettingsAccessResult::STATE_PLUGIN_DISABLED) {
            return null;
        }

        $message = trim((string) ($access->userMessage ?? ''));

        return $message !== '' ? $message : null;
    }

    private function settingsBlockedReason(HostPluginSettingsAccessResult $access): string
    {
        if ($access->state === HostPluginSettingsAccessResult::STATE_PLUGIN_DISABLED) {
            return (string) __('dberp.plugin_center.settings.plugin_disabled');
        }

        return $this->resolveSettingsHint($access)
            ?? (string) __('dberp.plugin_center.settings.unavailable');
    }

    private function resolvePrerequisiteAlertActions(): array
    {
        $actions = [];

        if (
            ! $this->entitlementGate->hasSiteBinding()
            || $this->entitlementGate->connectivityBlocksRemoteOperations()
        ) {
            if (SiteOnboardingCenterPage::canAccess()) {
                $actions[] = [
                    'label' => (string) __('dberp.plugin_center.prerequisite_alert.actions.open_site_onboarding'),
                    'url' => SiteOnboardingCenterPage::getUrl(panel: 'admin'),
                ];
            }

            return $actions;
        }

        if (HostSystemAuthorizationCenterPage::canAccess()) {
            $actions[] = [
                'label' => (string) __('dberp.plugin_center.prerequisite_alert.actions.open_authorization_center'),
                'url' => HostSystemAuthorizationCenterPage::getUrl(panel: 'admin'),
            ];
        }

        return $actions;
    }
}
