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

import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO;
import cn.axzo.workflow.common.model.response.mq.MessagePushDTO;
import cn.axzo.workflow.core.common.context.NoticeOperationContext;
import cn.axzo.workflow.core.engine.event.MessagePushEvent;
import cn.axzo.workflow.core.listener.AbstractBpmnEventListener;
import cn.axzo.workflow.core.listener.BpmnMessagePushEventListener;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_CANCEL;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.MULTI_INSTANCE_LOOP_COUNTER;
import static cn.axzo.workflow.common.constant.BpmnConstants.NUMBER_OF_INSTANCES;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_ACTIVITY_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_ACTIVITY_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_BUSINESS_KEY;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_BUSINESS_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_INITIATOR_USER_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_OPERATOR_TYPE;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_END_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_INSTANCE_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_INSTANCE_NAME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_RESULT;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_START_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_PROCESS_TENANT_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_START_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.VAR_TASK_USER_NAME;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_CARBON_COPY;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_CARBON_COPY_COMPLETE;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_NOTICE;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_PENDING;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_PENDING_COMPLETE;
import static cn.axzo.workflow.common.enums.ProcessMessagePushEventEnum.PROCESS_PUSH_SMS;

/**
 * 特殊的相外部传递需发送站内信/待办/短信的事件处理器
 *
 * @author wangli
 * @since 2023/11/22 10:52
 */
@Slf4j
@Component
public class RocketMqMessagePushEventListener extends AbstractBpmnEventListener<NoticeOperationContext> implements BpmnMessagePushEventListener, Ordered {
    @Resource
    private EventProducer<?> eventProducer;
    @Resource
    private BpmnProcessInstanceService processInstanceService;
    @Resource
    private HistoryService historyService;
    @Value("${sendMq:true}")
    private Boolean sendMQ;

    private static final List<String> REMOVE_KEYS = Lists.newArrayList(
            INTERNAL_INITIATOR,
            INTERNAL_END_USER_ID,
            INTERNAL_END_TENANT_ID,
            INTERNAL_END_USER_NAME,
            INTERNAL_DELETE_PROCESS_FLAG,
            INTERNAL_PROCESS_TYPE_CANCEL,
            INTERNAL_PROCESS_TYPE_REJECT,
            INTERNAL_SPECIFY_NEXT_APPROVER,
            BIZ_ORG_RELATION,
            INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO,
            INTERNAL_TASK_RELATION_ASSIGNEE_INFO,
            INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT,
            NUMBER_OF_INSTANCES,
            MULTI_INSTANCE_LOOP_COUNTER,
            TASK_COMPLETE_OPERATION_TYPE
    );

    @Override
    public void onNotice(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getNotice())
                || !StringUtils.hasText(event.getNoticeConfig().getNotice().getNoticeMessageId())
                || Objects.isNull(event.getAssigners())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onNotice...msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getNotice().getNoticeMessageId(), JSONUtil.toJsonStr(event.getAssigners()));
        if (Objects.nonNull(event.getNoticeConfig().getNotice())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getNotice().getNoticeMessageId(),
                    PROCESS_PUSH_NOTICE, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_PUSH_NOTICE);
        }
        log.info("RocketMqMessagePushEventListener#onNotice...end, msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getNotice().getNoticeMessageId(), JSONUtil.toJsonStr(event.getAssigners()));
    }


    @Override
    public void onPendingPush(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getPending())
                || !StringUtils.hasText(event.getNoticeConfig().getPending().getPendingMessageId())
                || Objects.isNull(event.getAssigners())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onPendingPush...msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getPending().getPendingMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
        if (Objects.nonNull(event.getNoticeConfig().getPending())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getPending().getPendingMessageId(),
                    PROCESS_PUSH_PENDING, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_PUSH_PENDING);

        }
        log.info("RocketMqMessagePushEventListener#onPendingPush...end, msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getPending().getPendingMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
    }

    @Override
    public void onPendingComplete(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getPending())
                || !StringUtils.hasText(event.getNoticeConfig().getPending().getPendingMessageId())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onPendingComplete...msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getPending().getPendingMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
        if (Objects.nonNull(event.getNoticeConfig().getPending())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getPending().getPendingMessageId(),
                    PROCESS_PUSH_PENDING_COMPLETE, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_PUSH_PENDING_COMPLETE);
        }
        log.info("RocketMqMessagePushEventListener#onPendingComplete...end, msgTemplateId: {}, receivePerson: " +
                            "{}", event.getNoticeConfig().getPending().getPendingMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
    }

    @Override
    public void onCarbonCopy(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getCarbonCopy())
                || !StringUtils.hasText(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onCarbonCopy... cc' templateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
        if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(),
                    PROCESS_CARBON_COPY, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_CARBON_COPY);
        }
        log.info("RocketMqMessagePushEventListener#onCarbonCopy...end, cc' templateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(),
                    JSONUtil.toJsonStr(event.getAssigners()));
    }

    /**
     * 完成抄送
     *
     * @param event
     */
    @Override
    public void onCarbonCopyComplete(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getCarbonCopy())
                || !StringUtils.hasText(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onCarbonCopyComplete... cc' templateId: {}",
                    event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId());
        if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId(),
                    PROCESS_CARBON_COPY_COMPLETE, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_CARBON_COPY_COMPLETE);
        }
        log.info("RocketMqMessagePushEventListener#onCarbonCopyComplete...end, cc' templateId: {}",
                    event.getNoticeConfig().getCarbonCopy().getCarbonCopyMessageId());
    }

    @Override
    public void onSms(MessagePushEvent event) {
        if (Objects.isNull(event.getNoticeConfig())
                || Objects.isNull(event.getNoticeConfig().getSms())
                || !StringUtils.hasText(event.getNoticeConfig().getSms().getSmsId())
                || Objects.isNull(event.getAssigners())) {
            return;
        }
        log.info("RocketMqMessagePushEventListener#onSms...msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getSms().getSmsId(), JSONUtil.toJsonStr(event.getAssigners()));
        if (Objects.nonNull(event.getNoticeConfig().getCarbonCopy())) {
            MessagePushDTO dto = build(event.getNoticeConfig().getSms().getSmsId(),
                    PROCESS_PUSH_SMS, event, collectionVariable(event));
            sendMessageQueue(dto, PROCESS_PUSH_SMS);
        }
        log.info("RocketMqMessagePushEventListener#onSms...end, msgTemplateId: {}, receivePerson: {}",
                    event.getNoticeConfig().getSms().getSmsId(), JSONUtil.toJsonStr(event.getAssigners()));
    }

    private Map<String, Object> collectionVariable(MessagePushEvent event) {
        Map<String, Object> variables = new HashMap<>();

        BpmnProcessInstanceVO processInstance = getContext().getInstanceVO(() -> getBpmnProcessInstanceVO(event));
        Map<String, Object> originVariables = processInstance.getVariables().entrySet().stream()
                .filter(e -> Objects.nonNull(e.getKey()) && Objects.nonNull(e.getValue()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(originVariables.get(INTERNAL_INITIATOR));
        if (Objects.isNull(initiator)) {
            initiator = new BpmnTaskDelegateAssigner();
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        variables.put(INTERNAL_INITIATOR, initiator);
        variables.put(VAR_PROCESS_TENANT_ID, event.getTenantId());
        variables.put(VAR_TASK_ID, event.getTaskId());

        // 产品需求参数
        variables.put(VAR_INITIATOR_USER_NAME, initiator.getAssignerName());
        variables.put(VAR_BUSINESS_NAME, processInstance.getCategoryDesc());
        variables.put(VAR_PROCESS_INSTANCE_NAME, processInstance.getName());
        variables.put(VAR_PROCESS_INSTANCE_ID, processInstance.getId());
        variables.put(VAR_PROCESS_START_TIME, sdf.format(processInstance.getCreateAt()));
        variables.put(VAR_BUSINESS_KEY, processInstance.getBusinessKey());
        if (Objects.nonNull(processInstance.getEndTime())) {
            variables.put(VAR_PROCESS_END_TIME, sdf.format(processInstance.getEndTime()));
        }
        variables.put(VAR_PROCESS_RESULT, processInstance.getResult().getDesc());
        if (StringUtils.hasText(event.getTaskId())) {
            List<HistoricTaskInstance> tasks =
                    historyService.createHistoricTaskInstanceQuery().taskId(event.getTaskId()).list();
            if (!CollectionUtils.isEmpty(tasks)) {
                // 应该只有 1 个, 但是为了防止有多个, 这里只取第一个
                List<BpmnTaskDelegateAssigner> assigners =
                        (List<BpmnTaskDelegateAssigner>) originVariables.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + tasks.get(0).getTaskDefinitionKey(), Collections.emptyList());
                assigners.stream().filter(i -> Objects.equals(i.buildAssigneeId(), tasks.get(0).getAssignee())).findAny()
                        .ifPresent(i -> {
                            variables.put(VAR_TASK_USER_NAME, i.getAssignerName());
                        });
                variables.put(VAR_ACTIVITY_ID, tasks.get(0).getTaskDefinitionKey());
                variables.put(VAR_ACTIVITY_NAME, tasks.get(0).getName());
                variables.put(VAR_TASK_START_TIME, sdf.format(tasks.get(0).getCreateTime()));
                // 操作类型 同意、驳回、加签、评论、转交等, 暂时不实现, 这里设置有很多问题
                variables.put(VAR_OPERATOR_TYPE, "");
            }
        }

        // 传递业务的参数
        REMOVE_KEYS.forEach(originVariables::remove);
        log.info("流程实例的参数: {}", JSONUtil.toJsonStr(originVariables));
        if (CollectionUtils.isEmpty(originVariables)) {
            return variables;
        }
        Iterator<Map.Entry<String, Object>> iterator = originVariables.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = iterator.next();
            REMOVE_KEYS.forEach(key -> {
                if (entry.getKey().contains(key)) {
                    iterator.remove();
                }
            });
        }

        variables.putAll(originVariables);
        return variables;
    }

    private BpmnProcessInstanceVO getBpmnProcessInstanceVO(MessagePushEvent event) {
        BpmnProcessInstanceQueryDTO queryDTO = new BpmnProcessInstanceQueryDTO();
        queryDTO.setProcessInstanceId(event.getProcessInstanceId());
        queryDTO.setTenantId(event.getTenantId());
        queryDTO.setHasVariable(true);
        return processInstanceService.getProcessInstanceVO(queryDTO);
    }

    public MessagePushDTO build(String templateId, ProcessMessagePushEventEnum type, MessagePushEvent event, Map<String,
            Object> variables) {
        return new MessagePushDTO()
                .setProcessInstanceId(event.getProcessInstanceId())
                .setType(type)
                .setTemplateId(templateId)
                .setTaskId(event.getTaskId())
                .setReceivePersons(event.getAssigners())
                .setVariables(variables);

    }

    public void sendMessageQueue(MessagePushDTO dto, ProcessMessagePushEventEnum 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;
    }
}
