/*
 * 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.admin.builder.controller;

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.ToolExecutionRequest;
import com.alibaba.cloud.ai.studio.runtime.domain.plugin.ToolExecutionResult;
import com.alibaba.cloud.ai.studio.runtime.domain.plugin.ToolQuery;
import com.alibaba.cloud.ai.studio.runtime.exception.BizException;
import com.alibaba.cloud.ai.studio.runtime.enums.ErrorCode;
import com.alibaba.cloud.ai.studio.runtime.enums.ToolTestStatus;
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.Result;
import com.alibaba.cloud.ai.studio.core.base.service.PluginService;
import com.alibaba.cloud.ai.studio.core.base.service.ToolExecutionService;
import com.alibaba.cloud.ai.studio.core.context.RequestContextHolder;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * Controller for managing plugins and tools. Provides REST APIs for plugin and tool CRUD
 * operations, testing, and publishing.
 *
 * @since 1.0.0.3
 */

@RestController
@Tag(name = "plugin")
@RequestMapping("/console/v1")
public class PluginController {

	/** Supported HTTP methods for tool requests */
	private static final List<String> SUPPORT_METHOD = Arrays.asList("post", "get");

	/** Service for plugin management operations */
	private final PluginService pluginService;

	/** Service for tool execution operations */
	private final ToolExecutionService toolExecutionService;

	public PluginController(PluginService pluginService, ToolExecutionService toolExecutionService) {
		this.pluginService = pluginService;
		this.toolExecutionService = toolExecutionService;
	}

	/**
	 * Creates a new plugin
	 * @param request Plugin creation request
	 * @return Plugin ID
	 */
	@PostMapping("/plugins")
	public Result<String> createPlugin(@RequestBody Plugin request) {
		RequestContext context = RequestContextHolder.getRequestContext();
		validatePlugin(request);

		String id = pluginService.createPlugin(request);
		return Result.success(context.getRequestId(), id);
	}

	/**
	 * Updates an existing plugin
	 * @param pluginId ID of the plugin to update
	 * @param request Plugin update request
	 */
	@PutMapping("/plugins/{pluginId}")
	public Result<Void> updatePlugin(@PathVariable("pluginId") String pluginId, @RequestBody Plugin request) {
		RequestContext context = RequestContextHolder.getRequestContext();
		validatePlugin(request);

		request.setPluginId(pluginId);
		pluginService.updatePlugin(request);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Deletes a plugin
	 * @param pluginId ID of the plugin to delete
	 */
	@DeleteMapping("/plugins/{pluginId}")
	public Result<Void> deletePlugin(@PathVariable("pluginId") String pluginId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		pluginService.deletePlugin(pluginId);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Retrieves a plugin by ID
	 * @param pluginId ID of the plugin to retrieve
	 * @return Plugin details
	 */
	@GetMapping("/plugins/{pluginId}")
	public Result<Plugin> getPlugin(@PathVariable("pluginId") String pluginId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		Plugin plugin = pluginService.getPlugin(pluginId);
		return Result.success(context.getRequestId(), plugin);
	}

	/**
	 * Lists plugins with pagination
	 * @param request Query parameters
	 * @return Paginated list of plugins
	 */
	@GetMapping("/plugins")
	public Result<PagingList<Plugin>> listPlugins(@ModelAttribute BaseQuery request) {
		RequestContext context = RequestContextHolder.getRequestContext();

		PagingList<Plugin> plugins = pluginService.listPlugins(request);
		return Result.success(context.getRequestId(), plugins);
	}

	/**
	 * Creates a new tool for a plugin
	 * @param pluginId ID of the parent plugin
	 * @param tool Tool creation request
	 * @return Tool ID
	 */
	@PostMapping("/plugins/{pluginId}/tools")
	public Result<String> createTool(@PathVariable("pluginId") String pluginId, @RequestBody Tool tool) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}
		validateTool(tool);

		tool.setPluginId(pluginId);
		String id = pluginService.createTool(tool);
		return Result.success(context.getRequestId(), id);
	}

	/**
	 * Updates an existing tool
	 * @param pluginId ID of the parent plugin
	 * @param toolId ID of the tool to update
	 * @param tool Tool update request
	 */
	@PutMapping("/plugins/{pluginId}/tools/{toolId}")
	public Result<String> updateTool(@PathVariable("pluginId") String pluginId, @PathVariable("toolId") String toolId,
			@RequestBody Tool tool) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		validateTool(tool);

		tool.setToolId(toolId);
		tool.setPluginId(pluginId);
		pluginService.updateTool(tool);

		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Deletes a tool
	 * @param pluginId ID of the parent plugin
	 * @param toolId ID of the tool to delete
	 */
	@DeleteMapping("/plugins/{pluginId}/tools/{toolId}")
	public Result<Void> deleteTool(@PathVariable("pluginId") String pluginId, @PathVariable("toolId") String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		pluginService.deleteTool(toolId);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Retrieves a tool by ID
	 * @param pluginId ID of the parent plugin
	 * @param toolId ID of the tool to retrieve
	 * @return Tool details
	 */
	@GetMapping("/plugins/{pluginId}/tools/{toolId}")
	public Result<Tool> getTool(@PathVariable("pluginId") String pluginId, @PathVariable("toolId") String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		Tool tool = pluginService.getTool(toolId);
		return Result.success(context.getRequestId(), tool);
	}

	/**
	 * Lists tools for a plugin with pagination
	 * @param pluginId ID of the parent plugin
	 * @param query Query parameters
	 * @return Paginated list of tools
	 */
	@GetMapping("/plugins/{pluginId}/tools")
	public Result<PagingList<Tool>> listTools(@PathVariable("pluginId") String pluginId,
			@ModelAttribute ToolQuery query) {
		RequestContext context = RequestContextHolder.getRequestContext();

		query.setPluginId(pluginId);
		PagingList<Tool> tools = pluginService.listTools(query);
		return Result.success(context.getRequestId(), tools);
	}

	/**
	 * Enables a tool
	 * @param toolId ID of the tool to enable
	 */
	@PostMapping("/tools/{toolId}/enable")
	public Result<Void> enableTool(@PathVariable("toolId") String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		pluginService.updateEnableStatus(toolId, true);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Disables a tool
	 * @param toolId ID of the tool to disable
	 */
	@PostMapping("/tools/{toolId}/disable")
	public Result<Void> disableTool(@PathVariable("toolId") String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		pluginService.updateEnableStatus(toolId, false);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Tests a tool execution
	 * @param pluginId ID of the parent plugin
	 * @param toolId ID of the tool to test
	 * @param request Tool execution request
	 * @return Tool execution result
	 */
	@PostMapping("/plugins/{pluginId}/tools/{toolId}/test")
	public Result<ToolExecutionResult> testTool(@PathVariable("pluginId") String pluginId,
			@PathVariable("toolId") String toolId, @RequestBody ToolExecutionRequest request) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		request.setPluginId(pluginId);
		request.setToolId(toolId);
		ToolExecutionResult result = toolExecutionService.executeTool(request);

		ToolTestStatus testStatus = result.isSuccess() ? ToolTestStatus.PASSED : ToolTestStatus.FAILED;
		pluginService.updateTestStatus(toolId, testStatus);

		return Result.success(context.getRequestId(), result);
	}

	/**
	 * Publishes a tool
	 * @param pluginId ID of the parent plugin
	 * @param toolId ID of the tool to publish
	 */
	@PostMapping("/plugins/{pluginId}/tools/{toolId}/publish")
	public Result<Void> publishTool(@PathVariable("pluginId") String pluginId, @PathVariable("toolId") String toolId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(pluginId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("pluginId"));
		}

		if (Objects.isNull(toolId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolId"));
		}

		pluginService.publishTool(toolId);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Queries tools by their IDs
	 * @param query Query containing tool IDs
	 * @return List of tools
	 */
	@PostMapping("/tools/query-by-ids")
	public Result<List<Tool>> queryToolsByIds(@RequestBody ToolQuery query) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(query.getToolIds()) || query.getToolIds().isEmpty()) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("toolIds"));
		}

		List<Tool> tools = pluginService.getTools(query.getToolIds());
		return Result.success(context.getRequestId(), tools);
	}

	/**
	 * Validates plugin creation/update request
	 * @param plugin Plugin to validate
	 */
	private void validatePlugin(Plugin plugin) {
		if (Objects.isNull(plugin) || Objects.isNull(plugin.getConfig())) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("plugin, config"));
		}

		if (StringUtils.isBlank(plugin.getName())) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("name"));
		}

		Plugin.PluginConfig pluginConfig = plugin.getConfig();
		if (StringUtils.isBlank(pluginConfig.getServer())) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("server"));
		}

		if (pluginConfig.getAuth() == null || pluginConfig.getAuth().getType() == null) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("auth_type"));
		}

		Plugin.ApiAuth apiAuth = pluginConfig.getAuth();
		if (apiAuth.getType() != Plugin.ApiAuthType.NONE) {
			if (apiAuth.getAuthorizationType() == null) {
				throw new BizException(ErrorCode.MISSING_PARAMS.toError("authorization_type"));
			}

			if (apiAuth.getAuthorizationType() == Plugin.AuthorizationType.CUSTOM) {
				if (apiAuth.getAuthorizationPosition() == null) {
					throw new BizException(ErrorCode.MISSING_PARAMS.toError("authorization_position"));
				}

				if (apiAuth.getAuthorizationKey() == null) {
					throw new BizException(ErrorCode.MISSING_PARAMS.toError("authorization_key"));
				}
			}

			if (apiAuth.getAuthorizationValue() == null) {
				throw new BizException(ErrorCode.MISSING_PARAMS.toError("authorization_value"));
			}
		}
	}

	/**
	 * Validates tool creation/update request
	 * @param tool Tool to validate
	 */
	private void validateTool(Tool tool) {
		if (Objects.isNull(tool) || Objects.isNull(tool.getConfig())) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("tool"));
		}

		String name = tool.getName();
		String description = tool.getDescription();
		if (StringUtils.isBlank(name)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("name"));
		}

		if (StringUtils.isBlank(description)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("description"));
		}

		String path = tool.getConfig().getPath();
		if (StringUtils.isBlank(path)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("path"));
		}
		if (!path.startsWith("/")) {
			throw new BizException(ErrorCode.INVALID_PARAMS.toError("path", "start with /"));
		}

		String requestMethod = tool.getConfig().getRequestMethod();
		requestMethod = StringUtils.lowerCase(requestMethod);
		if (StringUtils.isBlank(requestMethod)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("requestMethod"));
		}

		if (!SUPPORT_METHOD.contains(requestMethod)) {
			throw new BizException(ErrorCode.INVALID_PARAMS.toError("requestMethod", "method not supported"));
		}

		if ("post".equalsIgnoreCase(requestMethod)) {
			String contentType = tool.getConfig().getContentType();
			if (contentType == null) {
				throw new BizException(ErrorCode.MISSING_PARAMS.toError("contentType"));
			}

			if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType)
					&& !MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)) {
				throw new BizException(ErrorCode.INVALID_PARAMS.toError("contentType", "contentType not supported"));
			}
		}
	}

}
