package tech.easyflow.publicapi.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import tech.easyflow.approval.annotation.RequirePublishedAccess;
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.easyagentsflow.service.TinyFlowService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
import tech.easyflow.ai.easyagentsflow.upload.WorkflowApiPreparedUpload;
import tech.easyflow.ai.easyagentsflow.upload.WorkflowApiUploadLifecycleService;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.entity.WorkflowExecResult;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.WorkflowExecResultService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.service.WorkflowApiPermissionService;
import tech.easyflow.ai.utils.WorkFlowUtil;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.error.RequestIdContext;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.publicapi.dto.PublicWorkflowInfo;
import tech.easyflow.publicapi.dto.PublicWorkflowRunMetadata;
import tech.easyflow.publicapi.dto.PublicWorkflowRunResult;
import tech.easyflow.publicapi.dto.PublicWorkflowChainStatus;
import tech.easyflow.publicapi.dto.PublicWorkflowTopology;
import tech.easyflow.publicapi.service.WorkflowApiMultipartParameterMapper;
import tech.easyflow.publicapi.service.PublicWorkflowTopologyService;
import tech.easyflow.publicapi.service.PublicWorkflowStatusSanitizer;
import tech.easyflow.system.entity.SysApiKey;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 工作流
 */
@RequestMapping(value = "/public-api/workflow", produces = MediaType.APPLICATION_JSON_VALUE)
@RestController
public class PublicWorkflowController {

    @Resource
    private WorkflowService workflowService;
    @Resource
    private ChainExecutor chainExecutor;
    @Resource
    private TinyFlowService tinyFlowService;
    @Resource
    private WorkflowCheckService workflowCheckService;
    @Resource
    private WorkflowRunningParameterResolver workflowRunningParameterResolver;
    @Resource
    private WorkflowApiPermissionService workflowApiPermissionService;
    @Resource
    private WorkflowExecResultService workflowExecResultService;
    @Resource
    private WorkflowApiUploadLifecycleService workflowApiUploadLifecycleService;
    @Resource
    private PublicWorkflowTopologyService publicWorkflowTopologyService;
    @Resource
    private PublicWorkflowStatusSanitizer publicWorkflowStatusSanitizer;
    @Resource
    private WorkflowApiMultipartParameterMapper
            workflowApiMultipartParameterMapper;

    /**
     * 通过id或别名获取工作流详情
     *
     * @param key id或者别名
     * @return 工作流安全基础信息
     */
    @GetMapping(value = "/getByIdOrAlias")
    @RequirePublishedAccess(resourceType = "WORKFLOW", idExpr = "#key", denyMessage = "工作流尚未发布")
    public Result<PublicWorkflowInfo> getByIdOrAlias(
            @RequestParam
            @NotBlank(message = "key不能为空") String key,
            HttpServletRequest request) {
        workflowApiPermissionService.assertWorkflowApi(request.getHeader("ApiKey"), request.getRequestURI());
        Workflow workflow = workflowService.getPublishedDetail(key);
        assertStrictPublishedWorkflow(workflow);
        return Result.ok(PublicWorkflowInfo.from(workflow));
    }

    /**
     * 节点单独运行
     */
    @PostMapping("/singleRun")
    @SaCheckPermission("/api/v1/workflow/save")
    public Result<?> singleRun(
            @JsonBody(value = "workflowId", required = true) BigInteger workflowId,
            @JsonBody(value = "nodeId", required = true) String nodeId,
            @JsonBody("variables") Map<String, Object> variables) {

        Workflow workflow = workflowService.getById(workflowId);
        if (workflow == null) {
            return Result.fail(1, "工作流不存在");
        }
        if (variables == null) {
            variables = new HashMap<>();
        }
        variables = workflowRunningParameterResolver.normalizeRuntimeVariables(workflow.getContent(), variables);
        if (StpUtil.isLogin()) {
            variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount());
        }
        Map<String, Object> res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables);
        return Result.ok(res);
    }

    /**
     * 使用 JSON 参数异步运行已发布工作流。
     *
     * @param metadata 工作流 ID 与运行变量
     * @param request Servlet 请求
     * @return 保留执行 ID 字符串并附带工作流拓扑的响应
     */
    @PostMapping(
            value = "/runAsync",
            consumes = MediaType.APPLICATION_JSON_VALUE)
    @RequirePublishedAccess(
            resourceType = "WORKFLOW",
            idExpr = "#metadata.id",
            denyMessage = "工作流尚未发布")
    public PublicWorkflowRunResult runAsync(
            @Valid @RequestBody PublicWorkflowRunMetadata metadata,
            HttpServletRequest request) {
        SysApiKey apiKey = workflowApiPermissionService.assertWorkflowApi(request.getHeader("ApiKey"), request.getRequestURI());
        Workflow workflow = loadExecutableWorkflow(metadata.getId());
        PublicWorkflowTopology topology =
                publicWorkflowTopologyService.resolve(workflow);
        Map<String, Object> normalized =
                workflowRunningParameterResolver.normalizeRuntimeVariables(
                        workflow.getContent(),
                        metadata.getVariables());
        return executePublishedWorkflow(
                workflow,
                normalized,
                apiKey,
                topology,
                null);
    }

    /**
     * 通过单个 multipart 请求上传文件并异步运行工作流。
     *
     * <p>{@code metadata} Part 使用 JSON；工作流文件 Part 使用
     * {@code files.<开始节点文件参数名>}，同名 Part 可重复上传多个文件。</p>
     *
     * @param metadata 工作流 ID 与普通运行变量
     * @param multipartRequest multipart 请求
     * @param request Servlet 请求
     * @return 保留执行 ID 字符串并附带工作流拓扑的响应
     */
    @PostMapping(
            value = "/runAsync",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @RequirePublishedAccess(
            resourceType = "WORKFLOW",
            idExpr = "#metadata.id",
            denyMessage = "工作流尚未发布")
    public PublicWorkflowRunResult runAsyncMultipart(
            @Valid
            @RequestPart("metadata")
            PublicWorkflowRunMetadata metadata,
            MultipartHttpServletRequest multipartRequest,
            HttpServletRequest request) {
        SysApiKey apiKey =
                workflowApiPermissionService.assertWorkflowApi(
                        request.getHeader("ApiKey"),
                        request.getRequestURI());
        Workflow workflow = loadExecutableWorkflow(metadata.getId());
        PublicWorkflowTopology topology =
                publicWorkflowTopologyService.resolve(workflow);
        Map<String, List<MultipartFile>> fileParts =
                workflowApiMultipartParameterMapper.map(
                        multipartRequest.getMultiFileMap());
        WorkflowApiPreparedUpload preparedUpload =
                workflowApiUploadLifecycleService.prepare(
                        RequestIdContext.get(request),
                        workflow.getContent(),
                        metadata.getVariables(),
                        fileParts);
        try {
            return executePublishedWorkflow(
                    workflow,
                    preparedUpload.getVariables(),
                    apiKey,
                    topology,
                    executeId ->
                            workflowApiUploadLifecycleService.bindExecution(
                                    preparedUpload.getUploadId(),
                                    executeId));
        } catch (RuntimeException | Error error) {
            try {
                workflowApiUploadLifecycleService.abort(
                        preparedUpload.getUploadId());
            } catch (RuntimeException cleanupError) {
                error.addSuppressed(cleanupError);
            }
            throw error;
        }
    }

    /**
     * 获取工作流运行状态 - v2
     */
    @PostMapping("/getChainStatus")
    public Result<PublicWorkflowChainStatus> getChainStatus(
            @JsonBody(value = "executeId") String executeId,
            @JsonBody("nodes") List<NodeInfo> nodes,
            HttpServletRequest request) {
        SysApiKey apiKey = workflowApiPermissionService.assertWorkflowApi(request.getHeader("ApiKey"), request.getRequestURI());
        assertApiKeyExecutionOwnership(apiKey, executeId);
        ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
        if (res == null) {
            throw new BusinessException(
                    404,
                    40402,
                    "执行记录不存在、已过期或不可访问");
        }
        return Result.ok(publicWorkflowStatusSanitizer.sanitize(res));
    }

    /**
     * 恢复工作流运行 - v2
     */
    @PostMapping("/resume")
    public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
                               @JsonBody("confirmParams") Map<String, Object> confirmParams,
                               HttpServletRequest request) {
        SysApiKey apiKey = workflowApiPermissionService.assertWorkflowApi(request.getHeader("ApiKey"), request.getRequestURI());
        WorkflowExecResult execResult = assertApiKeyExecutionOwnership(apiKey, executeId);
        assertWorkflowExecutionResumable(execResult);
        if (!chainExecutor.resumeAsyncIfSuspended(
                executeId,
                confirmParams)) {
            throw new BusinessException(
                    409,
                    40901,
                    "当前执行状态不可恢复，仅暂停中的工作流允许恢复");
        }
        return Result.ok();
    }

    @GetMapping("getRunningParameters")
    @RequirePublishedAccess(resourceType = "WORKFLOW", idExpr = "#id", denyMessage = "工作流尚未发布")
    public Result<?> getRunningParameters(@RequestParam BigInteger id, HttpServletRequest request) {
        workflowApiPermissionService.assertWorkflowApi(request.getHeader("ApiKey"), request.getRequestURI());
        Workflow workflow = workflowService.getPublishedById(id);

        assertStrictPublishedWorkflow(workflow);
        workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
        Map<String, Object> res = workflowRunningParameterResolver.buildRunningParametersView(workflow);
        if (res == null) {
            throw new BusinessException(
                    500,
                    50001,
                    "工作流运行参数配置不可用");
        }
        return Result.ok(res);
    }

    /**
     * 构建 API Key 调用方的运行身份。
     *
     * @param apiKey 访问令牌
     * @return 工作流运行身份
     */
    private LoginAccount buildApiKeyLoginAccount(SysApiKey apiKey) {
        LoginAccount account = new LoginAccount();
        account.setId(apiKey.getId());
        account.setDeptId(apiKey.getDeptId() == null ? BigInteger.ZERO : apiKey.getDeptId());
        account.setTenantId(apiKey.getTenantId() == null ? BigInteger.ZERO : apiKey.getTenantId());
        account.setLoginName("apikey:" + apiKey.getId());
        account.setNickname("API 调用方");
        return account;
    }

    /**
     * 加载并校验可执行的已发布工作流。
     *
     * @param id 工作流 ID
     * @return 已发布工作流视图
     */
    private Workflow loadExecutableWorkflow(BigInteger id) {
        Workflow workflow = workflowService.getPublishedById(id);
        assertStrictPublishedWorkflow(workflow);
        workflowCheckService.checkOrThrow(
                workflow.getContent(),
                WorkflowCheckStage.PRE_EXECUTE,
                workflow.getId());
        return workflow;
    }

    /**
     * 使用统一身份和发布快照启动工作流。
     *
     * @param workflow 已发布工作流
     * @param variables 已归一化运行变量
     * @param apiKey API Key 实体
     * @param topology 对外公开拓扑
     * @param beforeStart 首个节点启动前回调
     * @return 执行响应
     */
    private PublicWorkflowRunResult executePublishedWorkflow(
            Workflow workflow,
            Map<String, Object> variables,
            SysApiKey apiKey,
            PublicWorkflowTopology topology,
            Consumer<String> beforeStart) {
        Map<String, Object> executionVariables =
                new LinkedHashMap<>();
        if (variables != null) {
            executionVariables.putAll(variables);
        }
        executionVariables.put(
                Constants.LOGIN_USER_KEY,
                buildApiKeyLoginAccount(apiKey));
        executionVariables.put(
                WorkFlowUtil.CREATED_KEY_MEMORY_KEY,
                WorkFlowUtil.API_KEY);
        String executeId = chainExecutor.executeAsync(
                PublishedWorkflowDefinitionIds.published(
                        workflow.getId().toString()),
                executionVariables,
                beforeStart);
        return PublicWorkflowRunResult.success(executeId, topology);
    }

    /**
     * 校验工作流 Public API 只能访问严格已发布且存在发布快照的工作流。
     *
     * @param workflow 工作流发布视图
     */
    private void assertStrictPublishedWorkflow(Workflow workflow) {
        if (workflow == null || !PublishStatus.PUBLISHED.getCode().equals(workflow.getPublishStatus())
                || workflow.getPublishedSnapshotJson() == null || workflow.getPublishedSnapshotJson().isEmpty()) {
            throw new BusinessException(
                    404,
                    40401,
                    "工作流不存在或当前不可公开调用");
        }
    }

    /**
     * 校验 Public Workflow API 后续操作只能作用于当前 API Key 发起的执行实例。
     *
     * @param apiKey 当前 API Key
     * @param executeId 执行 ID
     * @return 已通过归属校验的执行记录
     */
    private WorkflowExecResult assertApiKeyExecutionOwnership(SysApiKey apiKey, String executeId) {
        if (executeId == null || executeId.isBlank()) {
            throw new BusinessException(
                    400,
                    40017,
                    "executeId 不能为空");
        }
        WorkflowExecResult execResult = workflowExecResultService.getByExecKey(executeId);
        if (execResult == null) {
            throw new BusinessException(
                    404,
                    40402,
                    "工作流执行记录不存在、已过期或不可访问");
        }
        if (!WorkFlowUtil.API_KEY.equals(execResult.getCreatedKey())
                || apiKey == null
                || apiKey.getId() == null
                || !String.valueOf(apiKey.getId()).equals(execResult.getCreatedBy())) {
            throw new BusinessException(
                    404,
                    40402,
                    "工作流执行记录不存在、已过期或不可访问");
        }
        return execResult;
    }

    /**
     * 校验当前执行实例是否仍允许恢复。
     *
     * @param execResult 执行记录
     */
    private void assertWorkflowExecutionResumable(WorkflowExecResult execResult) {
        if (execResult == null || execResult.getWorkflowId() == null) {
            throw new BusinessException(
                    404,
                    40402,
                    "工作流执行记录不存在、已过期或不可访问");
        }
        Workflow workflow = workflowService.getById(execResult.getWorkflowId());
        if (workflow == null || !PublishStatus.PUBLISHED.getCode().equals(workflow.getPublishStatus())
                || workflow.getPublishedSnapshotJson() == null || workflow.getPublishedSnapshotJson().isEmpty()) {
            throw new BusinessException(
                    409,
                    40901,
                    "工作流已下线或当前执行状态不可恢复");
        }
    }
}
