package cn.axzo.workflow.core.engine.job;

import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.model.NextNodePreCheckAlterDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector;
import cn.axzo.workflow.core.listener.Alter;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.query.QueryProperty;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.job.service.JobHandler;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.variable.api.delegate.VariableScope;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify;

/**
 * 检查节点配置是否合法
 *
 * @author wangli
 * @since 2025-07-08 19:44
 */
@Slf4j
public class NextActivityConfigCheckJobHandler extends AbstractJobHandler implements JobHandler {
    public static final String TYPE = "next-activity-config-check";

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) {
        log.info("NextActivityConfigCheckJobHandler executing... 当前节点为: {}", job.getJobHandlerConfiguration());
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        FlowNodeForecastService forecastService = SpringContextUtils.getBean(FlowNodeForecastService.class);
        String currentActivityId = job.getJobHandlerConfiguration();
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(job.getProcessInstanceId()).singleResult();
        if (Objects.isNull(processInstance)) {
            return;
        }
        List<FlowElement> flowElements = forecastService.performProcessForecasting(job.getProcessInstanceId(), null, currentActivityId, false);
        if (CollectionUtils.isEmpty(flowElements)) {
            return;
        }
        try {
            doCheck(job, flowElements, processEngineConfiguration);
        } catch (Exception e) {
            // 有任何异常，则通过钉钉告警
            log.warn("NextActivityConfigCheckJobHandler msg: {}", e.getMessage(), e);
            CategoryService bean = SpringContextUtils.getBean(CategoryService.class);
            Optional<CategoryItemVO> bpmModelCategory = bean.get("bpm_model_category", processInstance.getProcessDefinitionKey());
            Alter alter = SpringContextUtils.getBean(Alter.class);
            FlowElement flowElement = ListUtils.emptyIfNull(flowElements).stream().filter(i -> i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask).findFirst().orElse(null);
            NextNodePreCheckAlterDTO alterDTO = new NextNodePreCheckAlterDTO();
            alterDTO.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
            alterDTO.setProcessDefinitionName(bpmModelCategory.orElse(new CategoryItemVO()).getLabel());
            alterDTO.setProcessInstanceId(job.getProcessInstanceId());
            alterDTO.setActivityId(Objects.nonNull(flowElement) ? flowElement.getId() : null);
            alterDTO.setErrorMsg(e.getMessage());
            alter.invoke(alterDTO);
        }
    }

    private void doCheck(JobEntity job, List<FlowElement> flowElements, ProcessEngineConfigurationImpl processEngineConfiguration) {
        AtomicReference<String> checkActivityId = new AtomicReference<>("");
        ListUtils.emptyIfNull(flowElements).stream()
                .filter(i -> i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask)
                .findFirst().ifPresent(flowElement -> {
                    log.info("NextActivityConfigCheckJobHandler, 实例 ID：{}, 计算节点为：{}", job.getJobHandlerConfiguration(), flowElement.getId());
                    getApprovalMethod(flowElement).ifPresent(method -> {
                        switch (method) {
                            case autoPassed:
                            case autoRejection:
                            case bizSpecify:
                            case nobody:
                                break;
                            default:
                                List<Execution> executions = processEngineConfiguration.getRuntimeService().createExecutionQuery()
                                        .processInstanceId(job.getProcessInstanceId())
                                        .orderBy((QueryProperty) () -> "START_TIME_").desc().list();
                                if (CollectionUtils.isEmpty(executions)) {
                                    return;
                                }
                                // 这里只会是 human 这一种情况， 因为 nobody 在转 BPMN 协议时，Activity 直接变成了 ReceiveTask 节点了。
                                List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
                                getApproverSpecify(flowElement).ifPresent(specify -> {
                                    assigners.addAll(approverSelect(specify.getType(), flowElement, (DelegateExecution) executions.get(0), true));
                                });

                                // 审批候选人为空时的兜底
                                emptyAssigneeHandle(assigners, flowElement, (DelegateExecution) executions.get(0));
                                break;
                        }
                    });
                    checkActivityId.set(flowElement.getId());
                });
        checkActivityId.get();
    }

    public List<BpmnTaskDelegateAssigner> approverSelect(String type, FlowElement flowElement,
                                                         DelegateExecution execution,
                                                         Boolean throwException) {
        List<BpmnTaskAssigneeSelector> selectors = new ArrayList<>(SpringContextUtils.getBeansOfType(BpmnTaskAssigneeSelector.class).values());
        List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
        ListUtils.emptyIfNull(selectors).forEach(select -> {
            if (select.support(type)) {
                log.info("NextActivityConfigCheckJobHandler-审批任务节点 Id:{}, Name: {}, 审批人指定枚举: {}", flowElement.getId(), flowElement.getName(), type);
                List<BpmnTaskDelegateAssigner> selected = select.select(flowElement, execution, throwException);
                log.info("NextActivityConfigCheckJobHandler-审批任务节点 Id:{} 的审批人集合为: {}", flowElement.getId(), JSONUtil.toJsonStr(selected));
                assigners.addAll(selected);
            }
        });
        return assigners;
    }

    /**
     * 计算节点的待审批人为空时, 执行模型配置中的审批人为空时的处理方式
     *
     * @param assigners   节点计算的待审批人集合, 可能为空
     * @param flowElement 当前节点
     * @param execution   当前执行实例
     */
    private void emptyAssigneeHandle(List<BpmnTaskDelegateAssigner> assigners, FlowElement flowElement,
                                     DelegateExecution execution) {
        // 审批人为空并且当前节点设置了自动跳过条件
        if (!CollectionUtils.isEmpty(assigners)) {
            return;
        }
        log.info("NextActivityConfigCheckJobHandler-当前节点id: [{}], name: [{}] 审批人为空, 将执行审批人为空的兜底配置!", flowElement.getId(), flowElement.getName());
        getApproverEmptyHandleType(flowElement).ifPresent(type -> {
            log.info("NextActivityConfigCheckJobHandler-节点兜底的配置模式:[{}]", type.getType());
            switch (type) {
                case autoPassed:
                case autoRejection:
                    break;
                case transferToAdmin:
                    try {
                        assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), flowElement,
                                execution, true));
                        if (CollectionUtils.isEmpty(assigners)) {
                            throw new IllegalArgumentException("审批人为空后转交管理员仍然为空");
                        }
                    } catch (Exception e) {
                        if (e instanceof IllegalArgumentException) {
                            throw new RuntimeException(e.getMessage());
                        } else {
                            throw new RuntimeException("审批人为空后转交管理员失败, 内部计算信息：" + e.getMessage());
                        }
                    }
                    break;
                case specifyAssignee:
                    try {
                        assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.specifyAssignee.getType(), flowElement,
                                execution, true));
                        if (CollectionUtils.isEmpty(assigners)) {
                            throw new IllegalArgumentException("审批人为空后转交指定人员仍然为空，可能被指定人员已离职");
                        }
                    } catch (Exception e) {
                        if (e instanceof IllegalArgumentException) {
                            throw new RuntimeException(e.getMessage());
                        } else {
                            throw new RuntimeException("审批人为空后转交指定人员失败，内部计算信息：" + e.getMessage());
                        }
                    }
                    break;
                default:
                    break;
            }

        });
    }
}
