<?php

declare(strict_types=1);

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

namespace App\Services\Plugin;

use App\Models\HostPluginOperationLog;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Throwable;

/**
 * 官方插件中心操作日志：业务表 + 技术诊断日志。
 */
final class HostPluginOperationLogger
{
    public const OPERATION_CATALOG_FETCH = 'catalog_fetch';

    public const OPERATION_CLAIM_CHECK = 'claim_check';

    public const OPERATION_DOWNLOAD_TOKEN = 'download_token';

    public const OPERATION_PACKAGE_DOWNLOAD = 'package_download';

    public const OPERATION_PACKAGE_VERIFICATION = 'package_verification';

    public const OPERATION_EXTRACTION = 'extraction';

    public const OPERATION_MANIFEST_VALIDATION = 'manifest_validation';

    public const OPERATION_RELEASE_PACKAGE_VALIDATION = 'release_package_validation';

    public const OPERATION_REGISTRATION = 'registration';

    public const OPERATION_INSTALLATION = 'installation';

    public const OPERATION_ENABLE = 'enable';

    public const OPERATION_DISABLE = 'disable';

    public const OPERATION_SETTINGS_ACCESS = 'settings_access';

    public const OPERATION_UPDATE_CHECK = 'update_check';

    public const OPERATION_UPDATE = 'update';

    public const OPERATION_UPDATE_EXTRACTION = 'update_extraction';

    public const OPERATION_UPDATE_MANIFEST_VALIDATION = 'update_manifest_validation';

    public const OPERATION_UPDATE_REPLACEMENT = 'update_replacement';

    public const OPERATION_UPDATE_RESTORE = 'update_restore';

    public const OPERATION_UPDATE_MIGRATION = 'update_migration';

    /**
     * 页面「操作记录与排障」中展示的成功记录（不含内部流水线步骤）。
     *
     * @return list<string>
     */
    public static function displayVisibleCompletedOperations(): array
    {
        return [
            self::OPERATION_INSTALLATION,
            self::OPERATION_ENABLE,
            self::OPERATION_DISABLE,
            self::OPERATION_UPDATE,
        ];
    }

    /**
     * 快照类操作：库中每种 operation 仅保留一条，写入时覆盖而非追加。
     * 页面亦仅展示最新一条（成功或失败），避免每次打开页面刷屏。
     *
     * @return list<string>
     */
    public static function displayLatestSnapshotOperations(): array
    {
        return [
            self::OPERATION_CATALOG_FETCH,
        ];
    }

    public function logStarted(string $operation, ?string $pluginCode = null, ?array $context = null): void
    {
        $this->persist('started', $operation, $pluginCode, null, null, null, $context);
    }

    public function logCompleted(string $operation, ?string $pluginCode = null, ?string $message = null, ?array $context = null): void
    {
        $this->persist('completed', $operation, $pluginCode, $message, null, null, $context);
    }

    public function logFailed(
        string $operation,
        ?string $pluginCode,
        string $message,
        ?string $errorCode = null,
        ?string $requestId = null,
        ?array $context = null,
        ?Throwable $exception = null,
    ): void {
        $technicalContext = array_merge($context ?? [], array_filter([
            'exception_class' => $exception !== null ? $exception::class : null,
            'exception_message' => $exception?->getMessage(),
        ]));

        $this->persist('failed', $operation, $pluginCode, $message, $errorCode, $requestId, $technicalContext);

        Log::warning('dberp.plugin_center.operation_failed', array_merge([
            'operation' => $operation,
            'plugin_code' => $pluginCode,
            'error_code' => $errorCode,
            'request_id' => $requestId,
        ], $technicalContext));
    }

    /**
     * @param  array<string, mixed>|null  $context
     */
    private function persist(
        string $resultStatus,
        string $operation,
        ?string $pluginCode,
        ?string $message,
        ?string $errorCode,
        ?string $requestId,
        ?array $context,
    ): void {
        if (! $this->tableReady()) {
            return;
        }

        if ($this->isSnapshotOperation($operation)) {
            $this->persistSnapshot(
                $resultStatus,
                $operation,
                $pluginCode,
                $message,
                $errorCode,
                $requestId,
                $context,
            );

            return;
        }

        if (! $this->shouldPersistAppendLog($resultStatus, $operation)) {
            return;
        }

        $this->persistAppend(
            $resultStatus,
            $operation,
            $pluginCode,
            $message,
            $errorCode,
            $requestId,
            $context,
        );
    }

    /**
     * 追加类操作入库策略：与「操作记录与排障」展示范围对齐，避免内部流水线成功步骤刷屏占库。
     */
    private function shouldPersistAppendLog(string $resultStatus, string $operation): bool
    {
        if ($resultStatus === 'started') {
            return false;
        }

        if ($resultStatus === 'failed') {
            return true;
        }

        if ($resultStatus === 'completed') {
            return in_array($operation, self::displayVisibleCompletedOperations(), true);
        }

        return false;
    }

    private function isSnapshotOperation(string $operation): bool
    {
        return in_array($operation, self::displayLatestSnapshotOperations(), true);
    }

    /**
     * @param  array<string, mixed>|null  $context
     */
    private function persistAppend(
        string $resultStatus,
        string $operation,
        ?string $pluginCode,
        ?string $message,
        ?string $errorCode,
        ?string $requestId,
        ?array $context,
    ): void {
        HostPluginOperationLog::query()->create([
            'plugin_code' => $pluginCode,
            'operation' => $operation,
            'result_status' => $resultStatus,
            'message' => $message,
            'error_code' => $errorCode,
            'request_id' => $requestId,
            'operator_id' => Auth::id(),
            'context' => $this->sanitizeContext($context),
        ]);
    }

    /**
     * @param  array<string, mixed>|null  $context
     */
    private function persistSnapshot(
        string $resultStatus,
        string $operation,
        ?string $pluginCode,
        ?string $message,
        ?string $errorCode,
        ?string $requestId,
        ?array $context,
    ): void {
        $payload = [
            'plugin_code' => $pluginCode,
            'result_status' => $resultStatus,
            'message' => $message,
            'error_code' => $errorCode,
            'request_id' => $requestId,
            'operator_id' => Auth::id(),
            'context' => $this->sanitizeContext($context),
        ];

        $existing = HostPluginOperationLog::query()
            ->where('operation', $operation)
            ->orderByDesc('id')
            ->first();

        if ($existing !== null) {
            HostPluginOperationLog::query()
                ->where('operation', $operation)
                ->where('id', '!=', $existing->id)
                ->delete();

            $existing->forceFill(array_merge($payload, ['created_at' => now()]))->save();

            return;
        }

        HostPluginOperationLog::query()->create(array_merge(
            ['operation' => $operation],
            $payload,
        ));
    }

    /**
     * @param  array<string, mixed>|null  $context
     * @return array<string, mixed>|null
     */
    private function sanitizeContext(?array $context): ?array
    {
        if ($context === null || $context === []) {
            return null;
        }

        $sanitized = [];

        foreach ($context as $key => $value) {
            if (! is_string($key)) {
                continue;
            }

            if (in_array($key, ['download_token', 'signature', 'checksum', 'internal_url', 'download_url'], true)) {
                continue;
            }

            $sanitized[$key] = $value;
        }

        return $sanitized === [] ? null : $sanitized;
    }

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