package cn.axzo.workflow.server.controller.listener.activity;

import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.workflow.common.enums.ProcessActivityEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO;
import cn.axzo.workflow.common.model.response.mq.ProcessActivityDTO;
import cn.axzo.workflow.core.common.context.ActivityOperationContext;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEvent;
import cn.axzo.workflow.core.listener.AbstractBpmnEventListener;
import cn.axzo.workflow.core.listener.BpmnActivityEventListener;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_END;
import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_START;
import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_TAKE;
import static cn.axzo.workflow.common.enums.ProcessActivityEventEnum.PROCESS_ACTIVITY_WAIT_ASSIGNEE;

/**
 * 自定义实现的活动节点监听器, 实现了 Ordered 接口
 * <p>
 * 注意:Order 值越小,优先级越高
 *
 * @author wangli
 * @since 2023/7/24 17:33
 */
@Slf4j
@Component
public class RocketMqBpmActivityEventListener extends AbstractBpmnEventListener<ActivityOperationContext> implements BpmnActivityEventListener, Ordered {
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ExtAxHiTaskInstService hiTaskInstService;
    @Resource
    private EventProducer<?> eventProducer;
    @Value("${sendMq:true}")
    private Boolean sendMQ;

    @Override
    public void onStart(DelegateExecution execution) {
        log.info("RocketMqBpmActivityEventListener#onStart...activityId: {}", execution.getCurrentActivityId());
        ProcessActivityDTO dto = build(PROCESS_ACTIVITY_START, execution);
        sendMessageQueue(dto, PROCESS_ACTIVITY_START);
        log.info("RocketMqBpmActivityEventListener#onStart...end, activityId: {}",
                    execution.getCurrentActivityId());
    }


    @Override
    public void onWaitAssignee(BizSpecifyAssigneeEvent event) {
        log.info("RocketMqBpmActivityEventListener#onWaitAssignee...activityId: {}", event.getActivityId());
        // 特殊的自定义业务事件, 不能使用公共的 build 方法
        ProcessActivityDTO dto = new ProcessActivityDTO();
        dto.setType(PROCESS_ACTIVITY_WAIT_ASSIGNEE);
        dto.setActivityId(event.getActivityId());
        dto.setActivityName(event.getActivityName());
        dto.setProcessInstanceId(event.getProcessInstanceId());
        dto.setProcessDefinitionId(event.getProcessDefinitionId());
        ProcessInstance processInstance =
                runtimeService.createProcessInstanceQuery().processInstanceId(event.getProcessInstanceId()).singleResult();
        if (Objects.nonNull(processInstance)) {
            dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
            dto.setBusinessKey(processInstance.getBusinessKey());
        }
        dto.setTriggerId(event.getExecutionId());
        dto.setVariables(event.getVariables());
        String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION);
        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }
        dto.setWorkflowEngineVersion(version);
        sendMessageQueue(dto, PROCESS_ACTIVITY_WAIT_ASSIGNEE);
        log.info("RocketMqBpmActivityEventListener#onWaitAssignee...end, activityId: {}", event.getActivityId());
    }

    @Override
    public void onTake(DelegateExecution execution) {
        log.info("RocketMqBpmActivityEventListener#onTake...activityId: {}",
                    execution.getCurrentActivityId());
        ProcessActivityDTO dto = build(PROCESS_ACTIVITY_TAKE, execution);

        handlePassedAssignee(execution, dto);

        sendMessageQueue(dto, PROCESS_ACTIVITY_TAKE);
        log.info("RocketMqBpmActivityEventListener#onTake...end, activityId: {}",
                    execution.getCurrentActivityId());
    }

    @Override
    public void onEnd(DelegateExecution execution) {
        log.info("RocketMqMessagePushEventListener#onEnd...activityId: {}",
                    execution.getCurrentActivityId());
        ProcessActivityDTO dto = build(PROCESS_ACTIVITY_END, execution);

        handlePassedAssignee(execution, dto);

        sendMessageQueue(dto, PROCESS_ACTIVITY_END);
        log.info("RocketMqBpmActivityEventListener#onEnd...end, activityId: {}",
                    execution.getCurrentActivityId());
    }

    private ProcessActivityDTO build(ProcessActivityEventEnum type, DelegateExecution execution) {
        ProcessActivityDTO dto = new ProcessActivityDTO();
        dto.setType(type);
        dto.setProcessInstanceId(execution.getProcessInstanceId());
        dto.setBusinessKey(execution.getProcessInstanceBusinessKey());
        dto.setProcessDefinitionId(execution.getProcessDefinitionId());
        // 这个 ID 等于 ExecutionId
        dto.setTriggerId(execution.getId());
        dto.setActivityId(execution.getCurrentActivityId());
        dto.setActivityName(((ExecutionEntityImpl) execution).getCurrentActivityName());
        ProcessInstance processInstance =
                runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).includeProcessVariables().singleResult();
        if (Objects.nonNull(processInstance)) {
            dto.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
            dto.setBusinessKey(processInstance.getBusinessKey());
            dto.setVariables(processInstance.getProcessVariables());
        }
        String version = (String) runtimeService.getVariable(processInstance.getId(), WORKFLOW_ENGINE_VERSION);
        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }
        dto.setWorkflowEngineVersion(version);
        Process mainProcess = repositoryService.getBpmnModel(execution.getProcessDefinitionId()).getMainProcess();
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);
        return dto;
    }

    private void handlePassedAssignee(DelegateExecution execution, ProcessActivityDTO dto) {
        dto.setPassedAssigners(Collections.emptyList());
        String activityId = execution.getCurrentActivityId();
        ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
        searchDTO.setProcessInstanceId(execution.getProcessInstanceId());
        searchDTO.setTaskDefinitionKey(activityId);
        searchDTO.setStatus(APPROVED);
        List<ExtAxHiTaskInst> hiTasks = hiTaskInstService.queryList(searchDTO);
        if (!CollectionUtils.isEmpty(hiTasks)) {
            List<String> passedAssigneeList = hiTasks.stream()
                    .map(ExtAxHiTaskInst::getAssignee).distinct().collect(Collectors.toList());
            List<BpmnTaskDelegateAssigner> originAssignerList =
                    runtimeService.getVariable(execution.getProcessInstanceId(),
                            INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + activityId, List.class);
            if (!CollectionUtils.isEmpty(originAssignerList)) {
                dto.setPassedAssigners(originAssignerList.stream()
                        .filter(i -> passedAssigneeList.contains(i.buildAssigneeId()))
                        .collect(Collectors.toList()));
            }
        }
    }

    private void sendMessageQueue(ProcessActivityDTO dto, ProcessActivityEventEnum eventEnum) {
        if (!sendMQ) {
            return;
        }
        eventProducer.send(Event.builder()
                .shardingKey(dto.getProcessInstanceId())
                .eventCode(eventEnum.getEventCode())
                .targetId(dto.getProcessInstanceId())
                .targetType(eventEnum.getTag())
                .data(dto)
                .build());
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

}
