/*
 * Copyright (c) 2023 codvision.com All Rights Reserved.
 */

package com.codvision.workflow.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.codvision.auth.entity.vo.SysUserVO;
import com.codvision.auth.support.UserContext;
import com.codvision.base.support.QuartzJobManager;
import com.codvision.cachecore.utils.RedisUtil;
import com.codvision.commoncore.exception.BusinessException;
import com.codvision.commoncore.utils.Preconditions;
import com.codvision.workflow.config.WorkFlowServiceFactory;
import com.codvision.workflow.entity.WorkFlowData;
import com.codvision.workflow.entity.vo.WorkFlowViewerVO;
import com.codvision.workflow.job.WorkFlowGlobalTimeOutJob;
import com.codvision.workflow.service.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.codvision.auth.support.CacheContants.WORK_FLOW_STATUS;
import static com.codvision.workflow.constant.WorkFlowConstant.*;

/**
 * @author hxl
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WorkFlowInstanceServiceImpl extends WorkFlowServiceFactory implements WorkFlowInstanceService {

    private final QuartzJobManager quartzJobManager;
    private final WorkFlowDataService dataService;
    private final WorkFlowRecordService recordService;
    private final WorkFlowService workFlowService;
    private final WorkFlowSubDataService workFlowSubDataService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startProcessInstanceById(String processDefinitionId, Map<String, Object> formMap) {
        // 获取最新版本的流程定义信息
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId)
                .singleResult();
        Preconditions.checkArgument(processDefinition != null,"获取流程定义信息失败");
        Preconditions.checkArgument(!processDefinition.isSuspended(),"流程定义已被挂起,请先激活");
        try {
            // 设置流程发起人
            SysUserVO sysUserVO = UserContext.get();
            identityService.setAuthenticatedUserId(sysUserVO.getId().toString());
            workFlowService.initDefaultVariables(formMap);
            // 启动流程
            ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, formMap);
            WorkFlowData workFlowData = WorkFlowData.builder().definitionId(processDefinitionId).definitionKey(processInstance.getProcessDefinitionKey()).deployId(processDefinition.getDeploymentId())
                    .instanceId(processInstance.getId()).formDataJson(JSONUtil.toJsonStr(formMap)).creatorUid(sysUserVO.getId())
                    .creatorName(sysUserVO.getName()).creatorOid(sysUserVO.getActiveOid()).build();
            String status = RedisUtil.get(WORK_FLOW_STATUS + processInstance.getId());
            if (StringUtils.isNotBlank(status)) {
                workFlowData.setStatus(Integer.valueOf(status));
            }

            // 配置流程超时检查任务
            setTimeOutJob(processInstance, workFlowData);
            // 保存发起人表单信息
            dataService.save(workFlowData);
            // 初始化操作记录
            recordService.initStartRecords(processInstance.getId());
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException("流程启动失败");
        }
    }

    @SneakyThrows
    @SuppressWarnings("all")
    private void setTimeOutJob(ProcessInstance processInstance, WorkFlowData workFlowData) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        Map<String, List<ExtensionElement>> extensionElements = bpmnModel.getMainProcess().getExtensionElements();
        if (ObjectUtil.isEmpty(extensionElements)) {
            return;
        }

        List<ExtensionElement> elements = extensionElements.get(EXTENSION_ELEMENT_TIMIT_OUT);
        if (CollectionUtils.isEmpty(elements)) {
            return;
        }

        ExtensionElement extensionElement = elements.get(0);
        String attributeValue = extensionElement.getAttributeValue(null, EXTENSION_ELEMENT_TIMIT);
        if (StringUtils.isBlank(attributeValue)) {
            return;
        }

        Map<String, Object> paraMap = new HashMap<>(2);
        paraMap.put("instanceId", processInstance.getId());

        DateTime offsetDatetime = DateUtil.offsetMinute(processInstance.getStartTime(), Integer.parseInt(attributeValue));
        String deadlineTime = DateUtil.format(offsetDatetime, DatePattern.NORM_DATETIME_PATTERN);
        workFlowData.setDeadlineTime(deadlineTime);

        String cron = LocalDateTime.parse(deadlineTime, DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)).format(DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy"));
        quartzJobManager.addJob(WorkFlowGlobalTimeOutJob.class, WORK_FLOW_TIME_OUT_INSTANCE_ID + processInstance.getId(), WORK_FLOW_JOB_GROUP, cron, paraMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void del(List<String> instanceIds, String deleteReason) {
        if (CollectionUtils.isNotEmpty(instanceIds)) {
            for (String instanceId : instanceIds) {
                // 删除操作记录
                recordService.delAllByInstanceId(instanceId);
                // 删除数据
                dataService.delByInstanceId(instanceId);
                workFlowSubDataService.delAllByInstanceId(instanceId);
                // 查询历史数据
                HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId);
                if (historicProcessInstance.getEndTime() != null) {
                    historyService.deleteHistoricProcessInstance(historicProcessInstance.getId());
                } else {
                    // 删除流程实例
                    runtimeService.deleteProcessInstance(instanceId, deleteReason);
                    // 删除历史流程实例
                    historyService.deleteHistoricProcessInstance(instanceId);
                }
            }
        }

    }

    public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
        HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (Objects.isNull(historicProcessInstance)) {
            throw new BusinessException("流程实例不存在: " + processInstanceId);
        }
        return historicProcessInstance;
    }


    @Override
    @SneakyThrows
    public WorkFlowViewerVO flowPace(String instanceId, String deployId) {
        WorkFlowViewerVO workFlowViewerVO = new WorkFlowViewerVO();
        List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId)
                .finished()
                .list();
        List<String> historyElementIdList = listFinished.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
        workFlowViewerVO.setHistoryElementIdList(historyElementIdList);

        List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId)
                .unfinished()
                .list();
        List<String> activeElementIdList = listUnFinished.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
        workFlowViewerVO.setActiveElementIdList(activeElementIdList);

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        workFlowViewerVO.setXmlData(IOUtils.toString(inputStream, StandardCharsets.UTF_8));
        return workFlowViewerVO;
    }
}
