package tech.easyflow.admin.controller.ai;

import com.easyagents.flow.core.chain.ChainStatus;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import tech.easyflow.admin.service.ai.WorkflowChatEventStream;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
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.entity.Workflow;
import tech.easyflow.ai.entity.WorkflowExecResult;
import tech.easyflow.ai.entity.WorkflowExecStep;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.WorkflowExecResultService;
import tech.easyflow.ai.service.WorkflowExecStepService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.service.WorkflowShareService;
import tech.easyflow.ai.share.WorkflowSharePolicy;
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.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 工作流管理端与分享端的对话运行接口。
 */
@RestController
@RequestMapping("/api/v1/workflowChat")
public class WorkflowChatController {

    @Resource
    private WorkflowService workflowService;
    @Resource
    private WorkflowShareService workflowShareService;
    @Resource
    private WorkflowCheckService workflowCheckService;
    @Resource
    private WorkflowRunningParameterResolver parameterResolver;
    @Resource
    private ResourceAccessService resourceAccessService;
    @Resource
    private WorkflowChatEventStream eventStream;
    @Resource
    private ChainExecutor chainExecutor;
    @Resource
    private WorkflowExecResultService execResultService;
    @Resource
    private WorkflowExecStepService execStepService;

    /**
     * 获取工作流的对话运行描述和输入表单。
     *
     * @param workflowId 工作流 ID
     * @param request HTTP 请求
     * @return 对话运行描述
     */
    @GetMapping("/descriptor")
    public Result<Map<String, Object>> descriptor(
            BigInteger workflowId,
            HttpServletRequest request
    ) {
        Workflow workflow = loadRunnableWorkflow(workflowId, request);
        workflowCheckService.checkOrThrow(
                workflow.getContent(),
                WorkflowCheckStage.PRE_EXECUTE,
                workflow.getId()
        );
        Map<String, Object> descriptor =
                parameterResolver.buildRunningParametersView(workflow);
        if (descriptor == null) {
            throw new BusinessException("工作流输入配置无法解析");
        }
        descriptor.put("workflowId", workflow.getId());
        descriptor.put("publishStatus", workflow.getPublishStatus());
        descriptor.put("shareable", isStrictlyPublished(workflow));
        return Result.ok(descriptor);
    }

    /**
     * 启动工作流，并流式返回全部可见输出。
     *
     * @param workflowId 工作流 ID
     * @param variables 工作流运行变量
     * @param request HTTP 请求
     * @return 工作流 SSE 事件流
     */
    @PostMapping(
            value = "/run",
            produces = MediaType.TEXT_EVENT_STREAM_VALUE
    )
    public SseEmitter run(
            @JsonBody(value = "workflowId", required = true)
            BigInteger workflowId,
            @JsonBody("variables") Map<String, Object> variables,
            HttpServletRequest request
    ) {
        Workflow workflow = loadRunnableWorkflow(workflowId, request);
        workflowCheckService.checkOrThrow(
                workflow.getContent(),
                WorkflowCheckStage.PRE_EXECUTE,
                workflow.getId()
        );
        Map<String, Object> normalizedVariables =
                parameterResolver.normalizeRuntimeVariables(
                        workflow.getContent(),
                        variables
                );
        LoginAccount account = SaTokenUtil.getLoginAccount();
        normalizedVariables.put(Constants.LOGIN_USER_KEY, account);
        normalizedVariables.put(
                WorkFlowUtil.CREATED_KEY_MEMORY_KEY,
                hasChatShareKey(request)
                        ? WorkFlowUtil.WORKFLOW_CHAT_SHARE
                        : WorkFlowUtil.WORKFLOW_CHAT
        );
        return eventStream.start(
                isStrictlyPublished(workflow)
                        ? PublishedWorkflowDefinitionIds.published(workflowId.toString())
                        : workflowId.toString(),
                normalizedVariables
        );
    }

    /**
     * 取消当前用户发起的工作流执行。
     *
     * @param executeId 执行实例 ID
     * @return 是否完成取消状态转换
     */
    @PostMapping("/cancel")
    public Result<Boolean> cancel(
            @JsonBody(value = "executeId", required = true)
            String executeId
    ) {
        assertExecutionOwnership(executeId);
        return Result.ok(chainExecutor.cancel(executeId, "用户已中止运行"));
    }

    /**
     * 恢复当前用户发起并等待确认的工作流执行。
     *
     * @param executeId 执行实例 ID
     * @param confirmParams 确认参数
     * @return 空结果
     */
    @PostMapping("/resume")
    public Result<Void> resume(
            @JsonBody(value = "executeId", required = true)
            String executeId,
            @JsonBody("confirmParams")
            Map<String, Object> confirmParams
    ) {
        WorkflowExecResult record = assertExecutionOwnership(executeId);
        if (record.getStatus() != null
                && (record.getStatus() == ChainStatus.SUCCEEDED.getValue()
                || record.getStatus() == ChainStatus.FAILED.getValue()
                || record.getStatus() == ChainStatus.CANCELLED.getValue())) {
            throw new BusinessException("当前工作流执行已结束");
        }
        chainExecutor.resumeAsync(
                executeId,
                confirmParams == null
                        ? new LinkedHashMap<>()
                        : new LinkedHashMap<>(confirmParams)
        );
        return Result.ok();
    }

    /**
     * 获取当前用户工作流执行的运行详情。
     *
     * @param executeId 执行实例 ID
     * @return 执行记录和有序节点步骤
     */
    @GetMapping("/execution")
    public Result<Map<String, Object>> detail(String executeId) {
        WorkflowExecResult record = assertExecutionOwnership(executeId);
        List<WorkflowExecStep> steps = execStepService.list(
                QueryWrapper.create()
                        .eq(WorkflowExecStep::getRecordId, record.getId())
                        .orderBy(WorkflowExecStep::getStartTime, true)
        );
        List<Map<String, Object>> stepViews = new ArrayList<>(steps.size());
        for (WorkflowExecStep step : steps) {
            Map<String, Object> view = new LinkedHashMap<>();
            view.put("id", step.getId());
            view.put("attemptKey", step.getExecKey());
            view.put("nodeId", step.getNodeId());
            view.put("nodeName", step.getNodeName());
            view.put("input", step.getInput());
            view.put("output", step.getOutput());
            view.put("status", step.getStatus());
            view.put("errorInfo", step.getErrorInfo());
            view.put("startTime", step.getStartTime());
            view.put("endTime", step.getEndTime());
            view.put("execTime", step.getExecTime());
            stepViews.add(view);
        }

        Map<String, Object> recordView = new LinkedHashMap<>();
        recordView.put("executeId", record.getExecKey());
        recordView.put("workflowId", record.getWorkflowId());
        recordView.put("title", record.getTitle());
        recordView.put("status", record.getStatus());
        recordView.put("input", record.getInput());
        recordView.put("output", record.getOutput());
        recordView.put("errorInfo", record.getErrorInfo());
        recordView.put("startTime", record.getStartTime());
        recordView.put("endTime", record.getEndTime());
        recordView.put("execTime", record.getExecTime());

        Map<String, Object> detail = new LinkedHashMap<>();
        detail.put("record", recordView);
        detail.put("steps", stepViews);
        return Result.ok(detail);
    }

    /**
     * 加载可运行工作流，并校验直接访问或对话分享权限。
     * 管理端直接运行时，已发布工作流使用发布快照，未发布工作流使用当前内容；
     * 分享运行始终要求严格发布快照。
     *
     * @param workflowId 工作流 ID
     * @param request HTTP 请求
     * @return 可运行工作流视图
     */
    private Workflow loadRunnableWorkflow(
            BigInteger workflowId,
            HttpServletRequest request
    ) {
        if (workflowId == null) {
            throw new BusinessException("工作流ID不能为空");
        }
        LoginAccount account = SaTokenUtil.getLoginAccount();
        Workflow current = workflowService.getById(workflowId);
        if (current == null) {
            throw new BusinessException("工作流不存在");
        }
        boolean sharedRequest = hasChatShareKey(request);
        if (sharedRequest) {
            workflowShareService.assertChatShareAccess(
                    request.getHeader(
                            WorkflowSharePolicy.CHAT_SHARE_KEY_HEADER
                    ),
                    workflowId,
                    account.getTenantId()
            );
        } else {
            resourceAccessService.assertAccess(
                    CategoryResourceType.WORKFLOW,
                    current,
                    ResourceAction.USE,
                    "无权限运行工作流"
            );
        }

        Workflow published = workflowService.getPublishedById(workflowId);
        if (isStrictlyPublished(published)) {
            return published;
        }
        if (sharedRequest) {
            throw new BusinessException(
                    409,
                    409,
                    "工作流尚未发布或已下线"
            );
        }
        return current;
    }

    /**
     * 校验执行记录属于当前用户发起的工作流对话。
     *
     * @param executeId 执行实例 ID
     * @return 执行记录
     */
    private WorkflowExecResult assertExecutionOwnership(String executeId) {
        if (executeId == null || executeId.isBlank()) {
            throw new BusinessException("执行ID不能为空");
        }
        WorkflowExecResult record = execResultService.getByExecKey(executeId);
        if (record == null) {
            throw new BusinessException("工作流执行记录不存在，请稍后重试");
        }
        LoginAccount account = SaTokenUtil.getLoginAccount();
        boolean chatSource = WorkFlowUtil.WORKFLOW_CHAT.equals(
                record.getCreatedKey()
        ) || WorkFlowUtil.WORKFLOW_CHAT_SHARE.equals(record.getCreatedKey());
        if (!chatSource
                || account.getId() == null
                || !account.getId().toString().equals(
                record.getCreatedBy()
        )) {
            throw new BusinessException(
                    403,
                    403,
                    "无权限访问当前工作流执行记录"
            );
        }
        return record;
    }

    /**
     * 判断请求是否携带对话分享密钥。
     *
     * @param request HTTP 请求
     * @return 携带非空对话分享密钥时返回 {@code true}
     */
    private boolean hasChatShareKey(HttpServletRequest request) {
        String shareKey = request == null
                ? null
                : request.getHeader(
                WorkflowSharePolicy.CHAT_SHARE_KEY_HEADER
        );
        return shareKey != null && !shareKey.isBlank();
    }

    /**
     * 判断工作流是否可按严格发布快照运行。
     *
     * @param workflow 工作流
     * @return 已发布且存在快照时返回 {@code true}
     */
    private boolean isStrictlyPublished(Workflow workflow) {
        return workflow != null
                && PublishStatus.PUBLISHED.getCode().equals(
                workflow.getPublishStatus()
        )
                && workflow.getPublishedSnapshotJson() != null
                && !workflow.getPublishedSnapshotJson().isEmpty();
    }
}
