/*
 * Copyright 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.ai.studio.core.base.service.impl;

import com.alibaba.cloud.ai.studio.runtime.enums.ErrorCode;
import com.alibaba.cloud.ai.studio.runtime.enums.PluginStatus;
import com.alibaba.cloud.ai.studio.runtime.enums.PluginType;
import com.alibaba.cloud.ai.studio.runtime.enums.ToolStatus;
import com.alibaba.cloud.ai.studio.runtime.enums.ToolTestStatus;
import com.alibaba.cloud.ai.studio.runtime.exception.BizException;
import com.alibaba.cloud.ai.studio.runtime.domain.tool.ApiParameter;
import com.alibaba.cloud.ai.studio.runtime.domain.BaseQuery;
import com.alibaba.cloud.ai.studio.runtime.domain.PagingList;
import com.alibaba.cloud.ai.studio.runtime.domain.RequestContext;
import com.alibaba.cloud.ai.studio.runtime.domain.plugin.Plugin;
import com.alibaba.cloud.ai.studio.runtime.domain.plugin.Tool;
import com.alibaba.cloud.ai.studio.runtime.domain.plugin.ToolQuery;
import com.alibaba.cloud.ai.studio.runtime.utils.JsonUtils;
import com.alibaba.cloud.ai.studio.core.base.service.PluginService;
import com.alibaba.cloud.ai.studio.core.base.constants.CacheConstants;
import com.alibaba.cloud.ai.studio.core.context.RequestContextHolder;
import com.alibaba.cloud.ai.studio.core.base.entity.PluginEntity;
import com.alibaba.cloud.ai.studio.core.base.entity.ToolEntity;
import com.alibaba.cloud.ai.studio.core.base.manager.RedisManager;
import com.alibaba.cloud.ai.studio.core.base.mapper.PluginMapper;
import com.alibaba.cloud.ai.studio.core.base.mapper.ToolMapper;
import com.alibaba.cloud.ai.studio.core.utils.common.BeanCopierUtils;
import com.alibaba.cloud.ai.studio.core.utils.common.IdGenerator;
import com.alibaba.cloud.ai.studio.core.utils.api.OpenApiUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import static com.alibaba.cloud.ai.studio.core.base.constants.CacheConstants.*;

/**
 * Plugin service implementation for managing plugins and tools. Handles CRUD operations
 * for plugins and their associated tools.
 *
 * @since 1.0.0.3
 */
@Service
public class PluginServiceImpl extends ServiceImpl<PluginMapper, PluginEntity> implements PluginService {

	/** Tool data access mapper */
	private final ToolMapper toolMapper;

	/** Redis cache manager */
	private final RedisManager redisManager;

	public PluginServiceImpl(ToolMapper toolMapper, RedisManager redisManager) {
		this.toolMapper = toolMapper;
		this.redisManager = redisManager;
	}

	/**
	 * Creates a new plugin
	 * @param plugin Plugin information
	 * @return Generated plugin ID
	 */
	@Override
	public String createPlugin(Plugin plugin) {
		try {
			RequestContext context = RequestContextHolder.getRequestContext();

			// check if plugin name exists
			PluginEntity pluginEntity = getPluginByName(context.getWorkspaceId(), plugin.getName());
			if (pluginEntity != null) {
				throw new BizException(ErrorCode.PLUGIN_NAME_EXISTS.toError());
			}

			String pluginId = IdGenerator.idStr();
			PluginEntity entity = BeanCopierUtils.copy(plugin, PluginEntity.class);
			entity.setPluginId(pluginId);
			entity.setWorkspaceId(context.getWorkspaceId());

			String config = JsonUtils.toJson(plugin.getConfig());
			entity.setConfig(config);
			entity.setType(PluginType.CUSTOM);
			entity.setStatus(PluginStatus.NORMAL);

			entity.setGmtCreate(new Date());
			entity.setGmtModified(new Date());
			entity.setCreator(context.getAccountId());
			entity.setModifier(context.getAccountId());

			this.save(entity);

			String key = getPluginCacheKey(entity.getWorkspaceId(), entity.getPluginId());
			redisManager.put(key, entity);

			return pluginId;
		}
		catch (BizException e) {
			throw e;
		}
		catch (Exception e) {
			throw new BizException(ErrorCode.CREATE_PLUGIN_ERROR.toError(), e);
		}
	}

	/**
	 * Updates an existing plugin
	 * @param plugin Updated plugin information
	 */
	@Override
	public void updatePlugin(Plugin plugin) {
		try {
			RequestContext context = RequestContextHolder.getRequestContext();

			PluginEntity entity = getPluginById(context.getWorkspaceId(), plugin.getPluginId());
			if (entity == null) {
				throw new BizException(ErrorCode.PLUGIN_NOT_FOUND.toError());
			}

			// check if plugin name exists
			PluginEntity pluginEntity = getPluginByName(context.getWorkspaceId(), plugin.getName());
			if (pluginEntity != null && !pluginEntity.getId().equals(entity.getId())) {
				throw new BizException(ErrorCode.PLUGIN_NAME_EXISTS.toError());
			}

			entity.setName(plugin.getName());
			entity.setDescription(plugin.getDescription());

			String config = JsonUtils.toJson(plugin.getConfig());
			entity.setConfig(config);

			entity.setSource(plugin.getSource());
			entity.setGmtModified(new Date());
			entity.setModifier(context.getWorkspaceId());

			this.updateById(entity);

			String key = getPluginCacheKey(entity.getWorkspaceId(), entity.getPluginId());
			redisManager.put(key, entity);
		}
		catch (BizException e) {
			throw e;
		}
		catch (Exception e) {
			throw new BizException(ErrorCode.UPDATE_PLUGIN_ERROR.toError(), e);
		}
	}

	/**
	 * Deletes a plugin and its associated tools
	 * @param pluginId ID of the plugin to delete
	 */
	@Override
	public void deletePlugin(String pluginId) {
		RequestContext context = RequestContextHolder.getRequestContext();
		PluginEntity entity = getPluginById(context.getWorkspaceId(), pluginId);
		if (entity == null) {
			return;
		}

		// delete all tools first
		LambdaQueryWrapper<ToolEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ToolEntity::getPluginId, pluginId)
			.eq(ToolEntity::getWorkspaceId, context.getWorkspaceId())
			.ne(ToolEntity::getStatus, ToolStatus.DELETED.getStatus());

		List<ToolEntity> entities = toolMapper.selectList(queryWrapper);
		List<String> keys = new ArrayList<>();
		if (!CollectionUtils.isEmpty(entities)) {
			for (ToolEntity toolEntity : entities) {
				String key = getToolCacheKey(context.getWorkspaceId(), toolEntity.getToolId());
				keys.add(key);
			}
		}
		redisManager.delete(keys);

		LambdaUpdateWrapper<ToolEntity> updateWrapper = new LambdaUpdateWrapper<>();
		updateWrapper.eq(ToolEntity::getPluginId, pluginId)
			.eq(ToolEntity::getWorkspaceId, context.getWorkspaceId())
			.set(ToolEntity::getStatus, ToolStatus.DELETED.getStatus());

		toolMapper.update(updateWrapper);

		// delete from db
		entity.setStatus(PluginStatus.DELETED);
		entity.setGmtModified(new Date());
		entity.setModifier(context.getAccountId());
		this.updateById(entity);

		// delete from cache
		String cacheKey = getPluginCacheKey(context.getWorkspaceId(), entity.getPluginId());
		redisManager.delete(cacheKey);
	}

	/**
	 * Retrieves a plugin by ID
	 * @param pluginId Plugin ID
	 * @return Plugin information
	 */
	@Override
	public Plugin getPlugin(String pluginId) {
		RequestContext context = RequestContextHolder.getRequestContext();
		PluginEntity entity = getPluginById(context.getWorkspaceId(), pluginId);
		if (entity == null) {
			throw new BizException(ErrorCode.PLUGIN_NOT_FOUND.toError());
		}

		return toPluginDTO(entity);
	}

	/**
	 * Lists plugins with pagination
	 * @param query Query parameters including pagination info
	 * @return Paginated list of plugins
	 */
	@Override
	public PagingList<Plugin> listPlugins(BaseQuery query) {
		RequestContext context = RequestContextHolder.getRequestContext();

		LambdaQueryWrapper<PluginEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(PluginEntity::getWorkspaceId, context.getWorkspaceId());
		queryWrapper.ne(PluginEntity::getStatus, PluginStatus.DELETED.getStatus());
		if (StringUtils.isNotBlank(query.getName())) {
			queryWrapper.like(PluginEntity::getName, query.getName());
		}
		queryWrapper.orderByDesc(PluginEntity::getId);

		Page<PluginEntity> page = new Page<>(query.getCurrent(), query.getSize());
		IPage<PluginEntity> pageResult = this.page(page, queryWrapper);

		List<Plugin> plugins;
		if (CollectionUtils.isEmpty(pageResult.getRecords())) {
			plugins = new ArrayList<>();
		}
		else {
			plugins = pageResult.getRecords().stream().map(this::toPluginDTO).toList();
		}

		return new PagingList<>(query.getCurrent(), query.getSize(), pageResult.getTotal(), plugins);
	}

	/**
	 * Creates a new tool for a plugin
	 * @param tool Tool information
	 * @return Generated tool ID
	 */
	@Override
	public String createTool(Tool tool) {
		try {
			RequestContext context = RequestContextHolder.getRequestContext();
			Plugin plugin = getPlugin(tool.getPluginId());

			// check if tool name exists
			ToolEntity toolEntity = getToolByName(context.getWorkspaceId(), plugin.getPluginId(), tool.getName());
			if (toolEntity != null) {
				throw new BizException(ErrorCode.TOOL_NAME_EXISTS.toError());
			}

			Tool.ToolConfig config = tool.getConfig();
			List<ApiParameter> inputParams = config.getInputParams();
			if (!CollectionUtils.isEmpty(inputParams)) {
				for (ApiParameter apiParameter : inputParams) {
					String location = apiParameter.getLocation();
					if ("Get".equals(config.getRequestMethod()) && location.equals("Body")) {
						throw new BizException(
								ErrorCode.INVALID_PARAMS.toError("input_params", "Get method not support body params"));
					}
				}
			}

			// convert to swagger yaml
			String yaml = OpenApiUtils.buildOpenAPIYaml(plugin, tool);

			if (StringUtils.isBlank(yaml) || !CollectionUtils.isEmpty(OpenApiUtils.parseOpenAPIObject(yaml))) {
				throw new BizException(ErrorCode.BUILD_TOOL_SCHEMA_ERROR.toError());
			}

			String toolId = IdGenerator.idStr();
			ToolEntity entity = BeanCopierUtils.copy(tool, ToolEntity.class);
			entity.setToolId(toolId);
			entity.setPluginId(tool.getPluginId());
			entity.setWorkspaceId(context.getWorkspaceId());
			entity.setConfig(JsonUtils.toJson(tool.getConfig()));
			entity.setApiSchema(yaml);
			entity.setStatus(ToolStatus.DRAFT);
			entity.setEnabled(false);
			entity.setTestStatus(ToolTestStatus.NOT_TEST);

			entity.setGmtCreate(new Date());
			entity.setGmtModified(new Date());
			entity.setCreator(context.getAccountId());
			entity.setModifier(context.getAccountId());

			toolMapper.insert(entity);

			// cache it
			String key = getToolCacheKey(entity.getWorkspaceId(), entity.getToolId());
			redisManager.put(key, entity);

			return toolId;
		}
		catch (BizException e) {
			throw e;
		}
		catch (Exception e) {
			throw new BizException(ErrorCode.CREATE_TOOL_ERROR.toError(), e);
		}
	}

	/**
	 * Updates an existing tool
	 * @param tool Updated tool information
	 */
	@Override
	public void updateTool(Tool tool) {
		try {
			RequestContext context = RequestContextHolder.getRequestContext();

			String toolName = tool.getName();
			String pluginId = tool.getPluginId();

			ToolEntity entity = getToolById(context.getWorkspaceId(), tool.getToolId());
			if (entity == null) {
				throw new BizException(ErrorCode.TOOL_NOT_FOUND.toError());
			}

			Plugin plugin = getPlugin(pluginId);

			// check if tool name exists
			ToolEntity toolEntity = getToolByName(context.getWorkspaceId(), plugin.getPluginId(), toolName);
			if (toolEntity != null && !toolEntity.getId().equals(entity.getId())) {
				throw new BizException(ErrorCode.TOOL_NAME_EXISTS.toError());
			}

			Tool.ToolConfig config = tool.getConfig();
			List<ApiParameter> inputParams = config.getInputParams();
			if (!CollectionUtils.isEmpty(inputParams)) {
				for (ApiParameter apiParameter : inputParams) {
					String location = apiParameter.getLocation();
					if ("Get".equals(config.getRequestMethod()) && location.equals("Body")) {
						throw new BizException(
								ErrorCode.INVALID_PARAMS.toError("input_params", "Get method not support body params"));
					}
				}
			}

			// convert to swagger yaml
			String yaml = OpenApiUtils.buildOpenAPIYaml(plugin, tool);

			if (StringUtils.isBlank(yaml) || !CollectionUtils.isEmpty(OpenApiUtils.parseOpenAPIObject(yaml))) {
				throw new BizException(ErrorCode.BUILD_TOOL_SCHEMA_ERROR.toError());
			}

			entity.setName(toolName);
			entity.setDescription(tool.getDescription());
			entity.setConfig(JsonUtils.toJson(tool.getConfig()));
			entity.setApiSchema(yaml);
			entity.setGmtModified(new Date());
			entity.setModifier(context.getAccountId());

			if (entity.getStatus() == ToolStatus.PUBLISHED) {
				entity.setStatus(ToolStatus.PUBLISHED_EDITING);
			}

			toolMapper.updateById(entity);

			// cache it
			String key = getToolCacheKey(entity.getWorkspaceId(), entity.getToolId());
			redisManager.put(key, entity);
		}
		catch (BizException e) {
			throw e;
		}
		catch (Exception e) {
			throw new BizException(ErrorCode.UPDATE_TOOL_ERROR.toError(), e);
		}
	}

	/**
	 * Deletes a tool
	 * @param toolId ID of the tool to delete
	 */
	@Override
	public void deleteTool(String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();
		// delete from db
		ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
		if (entity == null) {
			return;
		}

		entity.setStatus(ToolStatus.DELETED);
		entity.setGmtModified(new Date());
		entity.setModifier(context.getAccountId());
		toolMapper.updateById(entity);

		// delete from cache
		String cacheKey = getToolCacheKey(context.getWorkspaceId(), entity.getToolId());
		redisManager.delete(cacheKey);
	}

	/**
	 * Retrieves a tool by ID
	 * @param toolId Tool ID
	 * @return Tool information with associated plugin
	 */
	@Override
	public Tool getTool(String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();
		ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
		if (entity == null) {
			throw new BizException(ErrorCode.TOOL_NOT_FOUND.toError());
		}
		Tool toolDTO = toToolDTO(entity);
		toolDTO.setPlugin(getPlugin(entity.getPluginId()));
		return toolDTO;
	}

	/**
	 * Lists tools with pagination
	 * @param query Query parameters including pagination info
	 * @return Paginated list of tools
	 */
	@Override
	public PagingList<Tool> listTools(ToolQuery query) {
		RequestContext context = RequestContextHolder.getRequestContext();

		LambdaQueryWrapper<ToolEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ToolEntity::getWorkspaceId, context.getWorkspaceId())
			.eq(ToolEntity::getPluginId, query.getPluginId())
			.ne(ToolEntity::getStatus, ToolStatus.DELETED.getStatus());

		if (StringUtils.isNotBlank(query.getName())) {
			queryWrapper.like(ToolEntity::getName, query.getName());
		}

		if (!CollectionUtils.isEmpty(query.getToolIds())) {
			queryWrapper.in(ToolEntity::getId, query.getToolIds());
		}

		queryWrapper.orderByDesc(ToolEntity::getId);

		Page<ToolEntity> page = new Page<>(query.getCurrent(), query.getSize());
		IPage<ToolEntity> pageResult = toolMapper.selectPage(page, queryWrapper);

		List<Tool> tools;
		if (CollectionUtils.isEmpty(pageResult.getRecords())) {
			tools = new ArrayList<>();
		}
		else {
			tools = pageResult.getRecords().stream().map(this::toToolDTO).toList();
		}

		return new PagingList<>(query.getCurrent(), query.getSize(), pageResult.getTotal(), tools);
	}

	/**
	 * Retrieves multiple tools by their IDs
	 * @param toolIds List of tool IDs
	 * @return List of tools with their associated plugins
	 */
	@Override
	public List<Tool> getTools(List<String> toolIds) {
		RequestContext context = RequestContextHolder.getRequestContext();

		List<String> needQueryToolIds = new ArrayList<>();
		List<Tool> tools = new ArrayList<>();
		// from cache first
		for (String toolId : toolIds) {
			ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
			if (entity != null && !CacheConstants.CACHE_EMPTY_ID.equals(entity.getId())) {
				Tool tool = toToolDTO(entity);
				Plugin plugin = getPlugin(entity.getPluginId());
				tool.setPlugin(plugin);

				tools.add(tool);
			}
			else {
				needQueryToolIds.add(toolId);
			}
		}

		if (needQueryToolIds.isEmpty()) {
			return tools;
		}

		ToolQuery toolQuery = ToolQuery.builder().toolIds(needQueryToolIds).size(50).fullFields(true).build();

		PagingList<Tool> toolPagingList = listTools(toolQuery);
		for (Tool tool : toolPagingList.getRecords()) {
			Plugin plugin = getPlugin(tool.getPluginId());
			tool.setPlugin(plugin);

			tools.add(tool);
		}

		return tools;
	}

	/**
	 * Updates the enabled status of a tool
	 * @param toolId Tool ID
	 * @param enabled New enabled status
	 */
	@Override
	public void updateEnableStatus(String toolId, Boolean enabled) {
		RequestContext context = RequestContextHolder.getRequestContext();

		ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
		if (entity == null) {
			throw new BizException(ErrorCode.TOOL_NOT_FOUND.toError());
		}

		entity.setEnabled(enabled);
		entity.setGmtModified(new Date());
		entity.setModifier(context.getAccountId());
		toolMapper.updateById(entity);

		// update cache
		String key = getToolCacheKey(context.getWorkspaceId(), toolId);
		redisManager.put(key, entity);
	}

	/**
	 * Updates the test status of a tool
	 * @param toolId Tool ID
	 * @param testStatus New test status
	 */
	@Override
	public void updateTestStatus(String toolId, ToolTestStatus testStatus) {
		RequestContext context = RequestContextHolder.getRequestContext();

		ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
		if (entity == null) {
			throw new BizException(ErrorCode.TOOL_NOT_FOUND.toError());
		}

		entity.setTestStatus(testStatus);
		entity.setGmtModified(new Date());
		entity.setModifier(context.getAccountId());

		toolMapper.updateById(entity);

		// update cache
		String key = getToolCacheKey(context.getWorkspaceId(), toolId);
		redisManager.put(key, entity);
	}

	/**
	 * Publishes a tool after testing
	 * @param toolId Tool ID
	 */
	@Override
	public void publishTool(String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		ToolEntity entity = getToolById(context.getWorkspaceId(), toolId);
		if (entity == null) {
			throw new BizException(ErrorCode.TOOL_NOT_FOUND.toError());
		}

		if (entity.getTestStatus() != ToolTestStatus.PASSED) {
			throw new BizException(ErrorCode.TOOL_NOT_TESTED.toError());
		}

		entity.setStatus(ToolStatus.PUBLISHED);
		entity.setEnabled(true);
		toolMapper.updateById(entity);

		// update cache
		String key = getToolCacheKey(context.getWorkspaceId(), toolId);
		redisManager.put(key, entity);
	}

	/**
	 * Retrieves a plugin entity from cache or database
	 * @param workspaceId Workspace ID
	 * @param pluginId Plugin ID
	 * @return Plugin entity or null if not found
	 */
	private PluginEntity getPluginById(String workspaceId, String pluginId) {
		String key = getPluginCacheKey(workspaceId, pluginId);

		PluginEntity entity = redisManager.get(key);
		if (entity != null) {
			if (CACHE_EMPTY_ID.equals(entity.getId())) {
				return null;
			}

			return entity;
		}

		LambdaQueryWrapper<PluginEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(PluginEntity::getPluginId, pluginId)
			.eq(PluginEntity::getWorkspaceId, workspaceId)
			.ne(PluginEntity::getStatus, PluginStatus.DELETED.getStatus());

		Optional<PluginEntity> entityOptional = this.getOneOpt(queryWrapper);
		if (entityOptional.isEmpty()) {
			entity = new PluginEntity();
			entity.setId(CacheConstants.CACHE_EMPTY_ID);
			redisManager.put(key, entity, CacheConstants.CACHE_EMPTY_TTL);

			return null;
		}

		entity = entityOptional.get();
		redisManager.put(key, entity);
		return entity;
	}

	/**
	 * Retrieves a plugin entity by name
	 * @param workspaceId Workspace ID
	 * @param pluginName Plugin name
	 * @return Plugin entity or null if not found
	 */
	private PluginEntity getPluginByName(String workspaceId, String pluginName) {
		LambdaQueryWrapper<PluginEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(PluginEntity::getWorkspaceId, workspaceId)
			.eq(PluginEntity::getName, pluginName)
			.ne(PluginEntity::getStatus, ToolStatus.DELETED.getStatus())
			.last("limit 1");

		return this.getOne(queryWrapper);
	}

	/**
	 * Generates cache key for plugin
	 * @param workspaceId Workspace ID
	 * @param pluginId Plugin ID
	 * @return Cache key string
	 */
	public static String getPluginCacheKey(String workspaceId, String pluginId) {
		return String.format(CACHE_PLUGIN_WORKSPACE_ID_PREFIX, workspaceId, pluginId);
	}

	/**
	 * Converts plugin entity to DTO
	 * @param entity Plugin entity
	 * @return Plugin DTO
	 */
	private Plugin toPluginDTO(PluginEntity entity) {
		if (entity == null) {
			return null;
		}

		Plugin plugin = BeanCopierUtils.copy(entity, Plugin.class);
		String config = entity.getConfig();
		if (StringUtils.isNotBlank(config)) {
			Plugin.PluginConfig pluginConfig = JsonUtils.fromJson(config, Plugin.PluginConfig.class);
			plugin.setConfig(pluginConfig);
		}

		return plugin;
	}

	/**
	 * Retrieves a tool entity from cache or database
	 * @param workspaceId Workspace ID
	 * @param toolId Tool ID
	 * @return Tool entity or null if not found
	 */
	private ToolEntity getToolById(String workspaceId, String toolId) {
		String key = getToolCacheKey(workspaceId, toolId);
		ToolEntity entity = redisManager.get(key);
		if (entity != null) {
			if (CACHE_EMPTY_ID.equals(entity.getId())) {
				return null;
			}

			return entity;
		}

		LambdaQueryWrapper<ToolEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ToolEntity::getToolId, toolId)
			.eq(ToolEntity::getWorkspaceId, workspaceId)
			.ne(ToolEntity::getStatus, ToolStatus.DELETED.getStatus());

		entity = toolMapper.selectOne(queryWrapper);
		if (entity == null) {
			entity = new ToolEntity();
			entity.setId(CacheConstants.CACHE_EMPTY_ID);
			redisManager.put(key, entity, CacheConstants.CACHE_EMPTY_TTL);
			return null;
		}

		redisManager.put(key, entity);
		return entity;
	}

	/**
	 * Retrieves a tool entity by name
	 * @param workspaceId Workspace ID
	 * @param pluginId Plugin ID
	 * @param toolName Tool name
	 * @return Tool entity or null if not found
	 */
	private ToolEntity getToolByName(String workspaceId, String pluginId, String toolName) {
		LambdaQueryWrapper<ToolEntity> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(ToolEntity::getWorkspaceId, workspaceId)
			.eq(ToolEntity::getPluginId, pluginId)
			.eq(ToolEntity::getName, toolName)
			.ne(ToolEntity::getStatus, ToolStatus.DELETED.getStatus())
			.last("limit 1");

		return toolMapper.selectOne(queryWrapper);
	}

	/**
	 * Generates cache key for tool
	 * @param workspaceId Workspace ID
	 * @param toolId Tool ID
	 * @return Cache key string
	 */
	public static String getToolCacheKey(String workspaceId, String toolId) {
		return String.format(CACHE_TOOL_WORKSPACE_ID_PREFIX, workspaceId, toolId);
	}

	/**
	 * Converts tool entity to DTO
	 * @param entity Tool entity
	 * @return Tool DTO
	 */
	private Tool toToolDTO(ToolEntity entity) {
		if (entity == null) {
			return null;
		}

		Tool tool = BeanCopierUtils.copy(entity, Tool.class);
		String config = entity.getConfig();
		if (StringUtils.isNotBlank(config)) {
			Tool.ToolConfig toolConfig = JsonUtils.fromJson(config, Tool.ToolConfig.class);
			tool.setConfig(toolConfig);
		}

		return tool;
	}

}
