package tech.easyflow.admin.controller.ai;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.mybatisflex.core.paginate.Page;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckResult;
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
import tech.easyflow.ai.easyagentsflow.service.CodeEngineCapabilityService;
import tech.easyflow.ai.easyagentsflow.service.TinyFlowService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.publish.WorkflowPublishAppService;
import tech.easyflow.ai.service.AiResourceApprovalStateService;
import tech.easyflow.ai.vo.OfflineImpactCheckVo;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.ai.service.BotWorkflowService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.WorkflowService;
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.controller.BaseCurdController;
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.enums.ResourceLookup;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.permission.resource.RequireResourceAccess;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.system.service.SysApiKeyService;

import javax.annotation.Resource;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 控制层。
 *
 * @author michael
 * @since 2024-08-23
 */
@RestController
@RequestMapping("/api/v1/workflow")
public class WorkflowController extends BaseCurdController<WorkflowService, Workflow> {
    private final ModelService modelService;

    @Resource
    private SysApiKeyService apiKeyService;
    @Resource
    private BotWorkflowService botWorkflowService;
    @Resource
    private ChainExecutor chainExecutor;
    @Resource
    private TinyFlowService tinyFlowService;
    @Resource
    private CodeEngineCapabilityService codeEngineCapabilityService;
    @Resource
    private WorkflowCheckService workflowCheckService;
    @Resource
    private WorkflowDatacenterContentService workflowDatacenterContentService;
    @Resource
    private WorkflowRunningParameterResolver workflowRunningParameterResolver;
    @Resource
    private ResourceAccessService resourceAccessService;
    @Resource
    private WorkflowVisibilityQueryHelper workflowVisibilityQueryHelper;
    @Resource
    private WorkflowPublishAppService workflowPublishAppService;
    @Resource
    private AiResourceApprovalStateService aiResourceApprovalStateService;
    @Resource
    private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;

    public WorkflowController(WorkflowService service, ModelService modelService) {
        super(service);
        this.modelService = modelService;
    }

    /**
     * 节点单独运行
     */
    @PostMapping("/singleRun")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.USE,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#workflowId",
            denyMessage = "无权限运行工作流"
    )
    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 = service.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);
    }

    /**
     * 运行工作流 - v2
     */
    @PostMapping("/runAsync")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.USE,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限运行工作流"
    )
    public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
                                   @JsonBody("variables") Map<String, Object> variables) {
        if (variables == null) {
            variables = new HashMap<>();
        }
        Workflow workflow = service.getById(id);
        if (workflow == null) {
            throw new RuntimeException("工作流不存在");
        }
        workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
        variables = workflowRunningParameterResolver.normalizeRuntimeVariables(workflow.getContent(), variables);
        if (StpUtil.isLogin()) {
            variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount());
        }
        String executeId = chainExecutor.executeAsync(id.toString(), variables);
        return Result.ok(executeId);
    }

    /**
     * 获取工作流运行状态 - v2
     */
    @PostMapping("/getChainStatus")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.USE,
            lookup = ResourceLookup.EXEC_KEY,
            idExpr = "#executeId",
            denyMessage = "无权限访问该执行记录"
    )
    public Result<ChainInfo> getChainStatus(@JsonBody(value = "executeId") String executeId,
                                            @JsonBody("nodes") List<NodeInfo> nodes) {
        ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
        return Result.ok(res);
    }

    /**
     * 恢复工作流运行 - v2
     */
    @PostMapping("/resume")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.USE,
            lookup = ResourceLookup.EXEC_KEY,
            idExpr = "#executeId",
            denyMessage = "无权限恢复工作流执行"
    )
    public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
                               @JsonBody("confirmParams") Map<String, Object> confirmParams) {
        chainExecutor.resumeAsync(executeId, confirmParams);
        return Result.ok();
    }

    @PostMapping("/importWorkFlow")
    @SaCheckPermission("/api/v1/workflow/save")
    public Result<Void> importWorkFlow(Workflow workflow, MultipartFile jsonFile) throws Exception {
        if (workflow.getId() != null) {
            Workflow sourceWorkflow = requireWorkflow(String.valueOf(workflow.getId()));
            resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, sourceWorkflow, ResourceAction.MANAGE, "无权限管理工作流");
        }
        InputStream is = jsonFile.getInputStream();
        String content = IoUtil.read(is, StandardCharsets.UTF_8);
        workflow.setContent(content);
        workflowCheckService.checkOrThrow(content, WorkflowCheckStage.SAVE, workflow.getId());
        save(workflow);
        return Result.ok();
    }

    @GetMapping("/exportWorkFlow")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.READ,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限访问工作流"
    )
    public Result<String> exportWorkFlow(BigInteger id) {
        Workflow workflow = service.getById(id);
        if (workflow == null) {
            throw new BusinessException("工作流不存在");
        }
        return Result.ok("", workflow.getContent());
    }

    @GetMapping("getRunningParameters")
    @SaCheckPermission("/api/v1/workflow/query")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.READ,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限访问工作流"
    )
    public Result<?> getRunningParameters(@RequestParam BigInteger id) {
        Workflow workflow = service.getById(id);

        if (workflow == null) {
            return Result.fail(1, "can not find the workflow by id: " + id);
        }
        workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
        Map<String, Object> res = workflowRunningParameterResolver.buildRunningParametersView(workflow);
        if (res == null) {
            return Result.fail(2, "节点配置错误，请检查! ");
        }
        return Result.ok(res);
    }

    /**
     * 提交发布审批。
     *
     * @param id 工作流 ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitPublishApproval")
    @SaCheckPermission("/api/v1/workflow/save")
    public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(
                workflowPublishAppService.submitPublishApproval(id),
                "已提交发布审批",
                "已直接发布"
        );
    }

    /**
     * 提交下线审批。
     *
     * @param id 工作流 ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitOfflineApproval")
    @SaCheckPermission("/api/v1/workflow/save")
    public Result<BigInteger> submitOfflineApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(
                workflowPublishAppService.submitOfflineApproval(id),
                "已提交下线审批",
                "已直接下线"
        );
    }

    /**
     * 检查工作流下线影响。
     *
     * @param id 工作流 ID
     * @return 下线影响结果
     */
    @GetMapping("/offlineImpactCheck")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.MANAGE,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限管理工作流"
    )
    public Result<OfflineImpactCheckVo> offlineImpactCheck(@RequestParam BigInteger id) {
        return Result.ok(workflowPublishAppService.checkOfflineImpact(id));
    }

    /**
     * 提交删除审批。
     *
     * @param id 工作流 ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitDeleteApproval")
    @SaCheckPermission("/api/v1/workflow/remove")
    public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(
                workflowPublishAppService.submitDeleteApproval(id),
                "已提交删除审批",
                "已直接删除"
        );
    }

    @GetMapping("/supportedCodeEngines")
    @SaCheckPermission("/api/v1/workflow/query")
    public Result<?> supportedCodeEngines() {
        return Result.ok(codeEngineCapabilityService.listSupportedCodeEngines());
    }

    @PostMapping("/check")
    @SaCheckPermission("/api/v1/workflow/save")
    public Result<WorkflowCheckResult> check(@JsonBody("id") BigInteger id,
                                             @JsonBody("content") String content,
                                             @JsonBody(value = "stage", required = true) String stage) {
        if (id != null) {
            Workflow workflow = requireWorkflow(String.valueOf(id));
            resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.MANAGE, "无权限管理工作流");
        }
        WorkflowCheckStage checkStage = WorkflowCheckStage.from(stage);
        WorkflowCheckResult checkResult;
        if (StringUtils.hasLength(content)) {
            checkResult = workflowCheckService.checkContent(content, checkStage, id);
        } else if (id != null) {
            checkResult = workflowCheckService.checkWorkflow(id, checkStage);
        } else {
            throw new BusinessException("id 与 content 不能同时为空");
        }
        return Result.ok(checkResult);
    }

    @Override
    @GetMapping("detail")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.READ,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限访问工作流"
    )
    public Result<Workflow> detail(String id) {
        Workflow workflow = service.getDetail(id);
        aiResourceApprovalStateService.fillWorkflowApprovalState(workflow);
        return Result.ok(workflow);
    }

    @GetMapping("/copy")
    @SaCheckPermission("/api/v1/workflow/save")
    @RequireResourceAccess(
            resource = CategoryResourceType.WORKFLOW,
            action = ResourceAction.READ,
            lookup = ResourceLookup.WORKFLOW_ID,
            idExpr = "#id",
            denyMessage = "无权限访问工作流"
    )
    public Result<Void> copy(BigInteger id) {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        Workflow workflow = service.getById(id);
        if (workflow == null) {
            throw new BusinessException("工作流不存在");
        }
        workflow.setId(null);
        workflow.setAlias(IdUtil.fastSimpleUUID());
        commonFiled(workflow, account.getId(), account.getTenantId(), account.getDeptId());
        service.save(workflow);
        return Result.ok();
    }

    @Override
    protected Result onSaveOrUpdateBefore(Workflow entity, boolean isSave) {
        normalizeVisibilityScope(entity, isSave);
        if (!isSave && entity.getId() != null) {
            Workflow existed = requireWorkflow(String.valueOf(entity.getId()));
            resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, existed, ResourceAction.MANAGE, "无权限管理工作流");
        }
        if (StringUtils.hasLength(entity.getContent())) {
            workflowCheckService.checkOrThrow(entity.getContent(), WorkflowCheckStage.SAVE, entity.getId());
        }

        String alias = entity.getAlias();
        if (StringUtils.hasLength(alias)) {
            Workflow workflow = service.getByAlias(alias);
            if (workflow == null) {
                return null;
            }
            if (isSave) {
                throw new BusinessException("别名已存在！");
            }
            BigInteger id = entity.getId();
            if (id.compareTo(workflow.getId()) != 0) {
                throw new BusinessException("别名已存在！");
            }
        } else {
            entity.setAlias(null);
        }
        return null;
    }

    @Override
    public Result<List<Workflow>> list(Workflow entity, Boolean asTree, String sortKey, String sortType) {
        QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
        workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
        applyPublishedOnlyFilter(queryWrapper);
        queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
        List<Workflow> workflows = service.list(queryWrapper);
        aiResourceApprovalStateService.fillWorkflowApprovalState(workflows);
        return Result.ok(workflows);
    }

    @Override
    protected Page<Workflow> queryPage(Page<Workflow> page, QueryWrapper queryWrapper) {
        workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
        applyPublishedOnlyFilter(queryWrapper);
        Page<Workflow> result = super.queryPage(page, queryWrapper);
        aiResourceApprovalStateService.fillWorkflowApprovalState(result.getRecords());
        aiResourceCreatorNameSupport.fillWorkflowCreatorNames(result.getRecords());
        return result;
    }

    @Override
    @PostMapping("remove")
    public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
        return Result.fail(1, "请使用发布状态操作删除工作流");
    }

    private Result<BigInteger> buildApprovalActionResult(ApprovalActionResult actionResult,
                                                         String approvalMessage,
                                                         String directMessage) {
        return Result.ok(
                actionResult.isApprovalRequired() ? approvalMessage : directMessage,
                actionResult.getInstanceId()
        );
    }

    @Override
    protected Result onRemoveBefore(Collection<Serializable> ids) {
        for (Serializable id : ids) {
            Workflow workflow = requireWorkflow(String.valueOf(id));
            resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.MANAGE, "无权限管理工作流");
        }
        QueryWrapper queryWrapper = QueryWrapper.create();
        queryWrapper.in("workflow_id", ids);
        boolean exists = botWorkflowService.exists(queryWrapper);
        if (exists) {
            return Result.fail(1, "此工作流还关联有bot，请先取消关联后再删除！");
        }
        return null;
    }

    private void normalizeVisibilityScope(Workflow entity, boolean isSave) {
        if (entity == null) {
            return;
        }
        if (!StringUtils.hasLength(entity.getVisibilityScope())) {
            if (isSave) {
                entity.setVisibilityScope(VisibilityScope.PRIVATE.name());
            }
            return;
        }
        entity.setVisibilityScope(VisibilityScope.from(entity.getVisibilityScope()).name());
    }

    private Workflow requireWorkflow(String idOrAlias) {
        Workflow workflow = service.getDetail(idOrAlias);
        if (workflow == null) {
            throw new BusinessException("工作流不存在");
        }
        return workflow;
    }

    private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        String publishedOnly = attributes.getRequest().getParameter("publishedOnly");
        if ("true".equalsIgnoreCase(publishedOnly)) {
            queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
        }
    }
}
