<?php

declare(strict_types=1);

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

namespace App\Services\Plugin;

use App\Exceptions\Plugin\HostPluginOperationException;
use App\Models\CorePlugin;
use App\Services\DberpPermissionService;
use DBCore\Plugin\Contracts\PluginManagerInterface;
use DBCore\Plugin\Enums\PluginStatus;
use DBCore\Support\Results\OperationResult;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * 官方插件启用/停用：仅变更运行时状态，不删除文件与业务数据。
 */
final class HostPluginLifecycleService
{
    public function __construct(
        private readonly DberpPermissionService $permissionService,
        private readonly PluginManagerInterface $pluginManager,
        private readonly DberpPluginDiscoveryManager $discoveryManager,
        private readonly HostPluginOperationLock $operationLock,
        private readonly HostPluginRuntimeCacheCleaner $runtimeCacheCleaner,
        private readonly HostPluginOperationLogger $operationLogger,
    ) {}

    public function enable(string $identifier): void
    {
        $normalized = trim($identifier);

        if ($normalized === '') {
            throw HostPluginOperationException::blocked(
                (string) __('dberp.plugin_center.lifecycle.invalid_plugin'),
                'invalid_plugin',
            );
        }

        $this->assertPermission(DberpPermissionService::ENABLE_HOST_PLUGIN);
        $this->assertNotLockedForMutation($normalized, 'enable');

        $this->operationLogger->logStarted(
            HostPluginOperationLogger::OPERATION_ENABLE,
            $normalized,
        );

        try {
            $this->discoveryManager->scan();

            $result = $this->pluginManager->enable($normalized);

            if ($result->getStatus() === 'failure') {
                throw HostPluginOperationException::failed(
                    $this->mapLifecycleFailureMessage($result, 'enable'),
                    'enable_failed',
                );
            }

            $this->clearPluginLastError($normalized);
            $this->runtimeCacheCleaner->clearAfterLifecycleChange($normalized);

            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_ENABLE,
                $normalized,
                (string) __('dberp.plugin_center.lifecycle.enable_success'),
            );
        } catch (HostPluginOperationException $exception) {
            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_ENABLE,
                $normalized,
                $exception->userFacingMessage(),
                $exception->errorCode(),
            );

            throw $exception;
        } catch (Throwable $exception) {
            Log::error('dberp.plugin_center.enable_failed', [
                'identifier' => $normalized,
                'exception_class' => $exception::class,
                'exception_message' => $exception->getMessage(),
            ]);

            $this->recordPluginLastError($normalized, (string) __('dberp.plugin_center.lifecycle.enable_failed'));

            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_ENABLE,
                $normalized,
                (string) __('dberp.plugin_center.lifecycle.enable_failed'),
                'enable_failed',
                exception: $exception,
            );

            throw HostPluginOperationException::failed(
                (string) __('dberp.plugin_center.lifecycle.enable_failed'),
                'enable_failed',
                previous: $exception,
            );
        }
    }

    public function disable(string $identifier): void
    {
        $normalized = trim($identifier);

        if ($normalized === '') {
            throw HostPluginOperationException::blocked(
                (string) __('dberp.plugin_center.lifecycle.invalid_plugin'),
                'invalid_plugin',
            );
        }

        $this->assertPermission(DberpPermissionService::DISABLE_HOST_PLUGIN);
        $this->assertNotLockedForMutation($normalized, 'disable');

        $this->operationLogger->logStarted(
            HostPluginOperationLogger::OPERATION_DISABLE,
            $normalized,
        );

        try {
            $this->discoveryManager->scan();

            $result = $this->pluginManager->disable($normalized);

            if ($result->getStatus() === 'failure') {
                throw HostPluginOperationException::failed(
                    $this->mapLifecycleFailureMessage($result, 'disable'),
                    'disable_failed',
                );
            }

            $this->runtimeCacheCleaner->clearAfterLifecycleChange($normalized);

            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_DISABLE,
                $normalized,
                (string) __('dberp.plugin_center.lifecycle.disable_success'),
            );
        } catch (HostPluginOperationException $exception) {
            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_DISABLE,
                $normalized,
                $exception->userFacingMessage(),
                $exception->errorCode(),
            );

            throw $exception;
        } catch (Throwable $exception) {
            Log::error('dberp.plugin_center.disable_failed', [
                'identifier' => $normalized,
                'exception_class' => $exception::class,
                'exception_message' => $exception->getMessage(),
            ]);

            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_DISABLE,
                $normalized,
                (string) __('dberp.plugin_center.lifecycle.disable_failed'),
                'disable_failed',
                exception: $exception,
            );

            throw HostPluginOperationException::failed(
                (string) __('dberp.plugin_center.lifecycle.disable_failed'),
                'disable_failed',
                previous: $exception,
            );
        }
    }

    private function assertPermission(string $permission): void
    {
        $user = Auth::user();

        if ($user === null || ! $this->permissionService->allows($user, $permission)) {
            throw HostPluginOperationException::blocked(
                (string) __('dberp.plugin_center.lifecycle.permission_denied'),
                'permission_denied',
            );
        }
    }

    private function assertNotLockedForMutation(string $identifier, string $action): void
    {
        if (! $this->operationLock->isLocked($identifier)) {
            return;
        }

        throw HostPluginOperationException::blocked(
            (string) __('dberp.plugin_center.lifecycle.task_in_progress'),
            'task_in_progress',
        );
    }

    private function mapLifecycleFailureMessage(OperationResult $result, string $action): string
    {
        $message = trim($result->getMessage());

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

        return match ($action) {
            'enable' => (string) __('dberp.plugin_center.lifecycle.enable_failed'),
            'disable' => (string) __('dberp.plugin_center.lifecycle.disable_failed'),
            default => (string) __('dberp.plugin_center.lifecycle.operation_failed'),
        };
    }

    private function clearPluginLastError(string $identifier): void
    {
        $record = CorePlugin::query()->where('identifier', $identifier)->first();

        if ($record === null) {
            return;
        }

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

        $record->fill(['meta' => $meta])->save();
    }

    private function recordPluginLastError(string $identifier, string $message): void
    {
        $record = CorePlugin::query()->where('identifier', $identifier)->first();

        if ($record === null) {
            return;
        }

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

        if ($record->status === PluginStatus::Enabled->value) {
            $record->fill([
                'status' => 'failed',
                'meta' => $meta,
            ])->save();

            return;
        }

        $record->fill(['meta' => $meta])->save();
    }
}
