/*
 * 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.exception.BizException;
import com.alibaba.cloud.ai.studio.runtime.enums.AppType;
import com.alibaba.cloud.ai.studio.runtime.enums.ErrorCode;
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.runtime.domain.app.AppQuery;
import com.alibaba.cloud.ai.studio.runtime.domain.app.Application;
import com.alibaba.cloud.ai.studio.runtime.domain.app.ApplicationVersion;
import com.alibaba.cloud.ai.studio.runtime.utils.JsonUtils;
import com.alibaba.cloud.ai.studio.core.base.service.AppService;
import com.alibaba.cloud.ai.studio.core.context.RequestContextHolder;
import com.alibaba.cloud.ai.studio.core.workflow.WorkflowConfig;
import com.alibaba.cloud.ai.studio.core.workflow.processor.ExecuteProcessor;
import com.alibaba.cloud.ai.studio.core.workflow.runtime.WorkflowExecuteManager;
import com.alibaba.cloud.ai.studio.admin.builder.annotation.ApiModelAttribute;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Objects;

/**
 * REST controller for managing agent applications. Handles CRUD operations, version
 * management, and application lifecycle.
 *
 * @since 1.0.0.3
 */

@RestController
@Tag(name = "app")
@RequestMapping("/console/v1/apps")
public class AppController {

	/** Service for application management */
	private final AppService appService;

	/** Manager for workflow execution */
	private final WorkflowExecuteManager workflowExecuteManager;

	public AppController(AppService appService, WorkflowExecuteManager workflowExecuteManager) {
		this.appService = appService;
		this.workflowExecuteManager = workflowExecuteManager;
	}

	/**
	 * Creates a new application
	 * @param app Application details
	 * @return Created application ID
	 */
	@PostMapping()
	public Result<String> createApp(@RequestBody Application app) {
		RequestContext context = RequestContextHolder.getRequestContext();
		if (Objects.isNull(app)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("app"));
		}

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

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

		String appId = appService.createApp(app);
		return Result.success(context.getRequestId(), appId);
	}

	/**
	 * Updates an existing application
	 * @param appId Application ID
	 * @param app Updated application details
	 * @return Success result
	 */
	@PutMapping("/{appId}")
	public Result<String> updateApp(@PathVariable("appId") String appId, @RequestBody Application app) {
		RequestContext context = RequestContextHolder.getRequestContext();

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

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

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

		app.setAppId(appId);
		appService.updateApp(app);
		return Result.success(context.getRequestId(), null);
	}

	/**
	 * Deletes an application
	 * @param appId Application ID
	 * @return Success result
	 */
	@DeleteMapping("/{appId}")
	public Result<Void> deleteApp(@PathVariable("appId") String appId) {
		RequestContext context = RequestContextHolder.getRequestContext();

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

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

	/**
	 * Retrieves an application by ID
	 * @param appId Application ID
	 * @return Application details
	 */
	@GetMapping("/{appId}")
	public Result<Application> getApplication(@PathVariable("appId") String appId) {
		RequestContext context = RequestContextHolder.getRequestContext();

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

		Application app = appService.getApp(appId);
		return Result.success(context.getRequestId(), app);
	}

	/**
	 * Lists applications with pagination
	 * @param query Query parameters
	 * @return Paginated list of applications
	 */
	@GetMapping()
	public Result<PagingList<Application>> listApps(@ApiModelAttribute AppQuery query) {
		RequestContext context = RequestContextHolder.getRequestContext();

		PagingList<Application> apps = appService.listApps(query);
		return Result.success(context.getRequestId(), apps);
	}

	/**
	 * Publishes an application
	 * @param appId Application ID
	 * @return Success result
	 */
	@PostMapping("/{appId}/publish")
	public Result<Void> publishApp(@PathVariable("appId") String appId) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(appId)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("appId"));
		}
		Application app = appService.getApp(appId);
		if (app.getType() == AppType.WORKFLOW) {
			ApplicationVersion applicationVersion = appService.getAppVersion(appId, "latest");
			WorkflowConfig appOrchestraConfig = JsonUtils.fromJson(applicationVersion.getConfig(),
					WorkflowConfig.class);
			ExecuteProcessor.CheckFlowParamResult checkFlowParamResult = workflowExecuteManager
				.checkWorkflowConfig(appOrchestraConfig);
			if (!checkFlowParamResult.isSuccess()) {
				StringBuilder stringBuilder = new StringBuilder();
				checkFlowParamResult.getCheckNodeParamResults()
					.forEach(nodeParamResult -> stringBuilder.append("Node【")
						.append(nodeParamResult.getNodeName())
						.append("】")
						.append("have a configuration error：\n")
						.append(String.join(";\n", nodeParamResult.getErrorInfos()))
						.append("\n"));
				throw new BizException(ErrorCode.WORKFLOW_CONFIG_ILLEGAL.toError(stringBuilder.toString()));
			}
		}

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

	/**
	 * Lists application versions
	 * @param appId Application ID
	 * @param query Query parameters
	 * @return Paginated list of versions
	 */
	@GetMapping("/{appId}/versions")
	public Result<PagingList<ApplicationVersion>> listAppVersions(@PathVariable("appId") String appId,
			@ApiModelAttribute AppQuery query) {

		RequestContext context = RequestContextHolder.getRequestContext();

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

		query.setAppId(appId);
		PagingList<ApplicationVersion> appVersions = appService.listAppVersions(query);
		return Result.success(context.getRequestId(), appVersions);
	}

	/**
	 * Gets a specific application version
	 * @param appId Application ID
	 * @param version Version number
	 * @return Version details
	 */
	@GetMapping("/{appId}/versions/{version}")
	public Result<ApplicationVersion> getApplication(@PathVariable("appId") String appId,
			@PathVariable("version") String version) {
		RequestContext context = RequestContextHolder.getRequestContext();

		if (Objects.isNull(appId) || Objects.isNull(version)) {
			throw new BizException(ErrorCode.MISSING_PARAMS.toError("appId or version"));
		}

		ApplicationVersion appVersion = appService.getAppVersion(appId, version);
		return Result.success(context.getRequestId(), appVersion);
	}

	/**
	 * Creates a copy of an application
	 * @param appId Application ID
	 * @return ID of the new application
	 */
	@PostMapping("/{appId}/copy")
	public Result<String> copyApp(@PathVariable("appId") String appId) {
		RequestContext context = RequestContextHolder.getRequestContext();

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

		String newAppId = appService.copyApp(appId);
		return Result.success(context.getRequestId(), newAppId);
	}

}
