package com.ankaibei.workFlow.common.flow.base.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ankaibei.workFlow.common.core.base.service.BaseService;
import com.ankaibei.workFlow.common.core.object.CallResult;
import com.ankaibei.workFlow.common.core.object.MyRelationParam;
import com.ankaibei.workFlow.common.core.util.MyDateUtil;
import com.ankaibei.workFlow.common.core.util.MyModelUtil;
import com.ankaibei.workFlow.common.flow.constant.FlowApprovalType;
import com.ankaibei.workFlow.common.flow.constant.FlowConstant;
import com.ankaibei.workFlow.common.flow.constant.FlowTaskStatus;
import com.ankaibei.workFlow.common.flow.exception.FlowOperationException;
import com.ankaibei.workFlow.common.flow.model.FlowTaskComment;
import com.ankaibei.workFlow.common.flow.model.FlowWorkOrder;
import com.ankaibei.workFlow.common.flow.model.FlowWorkOrderExt;
import com.ankaibei.workFlow.common.flow.service.FlowApiService;
import com.ankaibei.workFlow.common.flow.service.FlowWorkOrderService;
import com.ankaibei.workFlow.common.flow.vo.FlowWorkOrderVo;
import com.ankaibei.workFlow.common.redis.cache.SessionCacheHelper;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

public abstract class BaseFlowService<M, K extends Serializable> extends BaseService<M, K> {

    @Autowired
    private FlowApiService flowApiService;
    @Autowired
    private FlowWorkOrderService flowWorkOrderService;
    @Autowired
    private SessionCacheHelper cacheHelper;

    @Transactional(rollbackFor = Exception.class)
    public void startWithBusinessKey(String processDefinitionId, K dataId) {
        ProcessInstance instance = flowApiService.start(processDefinitionId, dataId);
        flowWorkOrderService.saveNew(instance, dataId, null, super.tableName);
    }

    @Transactional(rollbackFor = Exception.class)
    public void startAndTakeFirst(
            String processDefinitionId, K dataId, FlowTaskComment comment, JSONObject variables) {
        ProcessInstance instance = flowApiService.start(processDefinitionId, dataId);
        flowWorkOrderService.saveNew(instance, dataId, null, super.tableName);
        flowApiService.takeFirstTask(instance.getProcessInstanceId(), comment, variables);
        // 这里需要在创建工单后再次更新一下工单状态，在flowApiService.completeTask中的更新，
        // 因为当时没有创建工单对象，更新会不起任何作用，所以这里要补偿一下。
        Integer approvalStatus = MapUtil.getInt(variables, FlowConstant.LATEST_APPROVAL_STATUS_KEY);
        flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(instance.getId(), approvalStatus);
    }

    @Transactional(rollbackFor = Exception.class)
    public FlowWorkOrder saveNewDraftAndStartProcess(String processDefinitionId, String masterData, String slaveData) {
        ProcessInstance instance = flowApiService.start(processDefinitionId, null);
        Map<String, Object> variableMap = flowApiService.initAndGetProcessInstanceVariables(processDefinitionId);
        flowApiService.setProcessInstanceVariables(instance.getProcessInstanceId(), variableMap);
        return flowWorkOrderService.saveNewWithDraft(instance, null, super.tableName, masterData, slaveData);
    }

    @Transactional(rollbackFor = Exception.class)
    public void takeFirstTask(
            String processInstanceId, String taskId, K dataId, FlowTaskComment comment, JSONObject variables) {
        Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId);
        flowApiService.setBusinessKeyForProcessInstance(processInstanceId, dataId);
        ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId);
        FlowWorkOrder flowWorkOrder =
                flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(instance.getProcessInstanceId());
        if (flowWorkOrder == null) {
            flowWorkOrderService.saveNew(instance, dataId, null, super.tableName);
        } else {
            flowWorkOrder.setBusinessKey(dataId.toString());
            flowWorkOrder.setUpdateTime(new Date());
            flowWorkOrder.setFlowStatus(FlowTaskStatus.SUBMITTED);
            flowWorkOrderService.updateById(flowWorkOrder);
        }
        flowApiService.completeTask(task, comment, variables);
    }

    @Transactional(rollbackFor = Exception.class)
    public void takeTask(Task task, K dataId, FlowTaskComment comment, JSONObject variables) {
        int flowStatus = FlowTaskStatus.APPROVING;
        if (comment.getApprovalType().equals(FlowApprovalType.REFUSE)) {
            flowStatus = FlowTaskStatus.REFUSED;
        } else if (comment.getApprovalType().equals(FlowApprovalType.STOP)) {
            flowStatus = FlowTaskStatus.FINISHED;
        }
        if (comment.getApprovalType().equals(FlowApprovalType.STOP)) {
            Integer s = MapUtil.getInt(variables, FlowConstant.LATEST_APPROVAL_STATUS_KEY);
            flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), s);
            CallResult stopResult = flowApiService.stopProcessInstance(
                    task.getProcessInstanceId(), comment.getTaskComment(), flowStatus);
            if (!stopResult.isSuccess()) {
                throw new FlowOperationException(stopResult.getErrorMessage());
            }
        } else {
            flowWorkOrderService.updateFlowStatusByProcessInstanceId(task.getProcessInstanceId(), flowStatus);
            flowApiService.completeTask(task, comment, variables);
        }
    }

    public void buildDraftData(List<FlowWorkOrderVo> draftWorkOrderList) {
        if (CollUtil.isEmpty(draftWorkOrderList)) {
            return;
        }
        Set<String> workOrderIdSet = draftWorkOrderList.stream()
                .map(FlowWorkOrderVo::getWorkOrderId).collect(Collectors.toSet());
        List<FlowWorkOrderExt> workOrderExtList =
                flowWorkOrderService.getFlowWorkOrderExtByWorkFlowIds(workOrderIdSet);
        Map<String, FlowWorkOrderExt> workOrderExtMap = workOrderExtList.stream()
                .collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c));
        for (FlowWorkOrderVo workOrder : draftWorkOrderList) {
            FlowWorkOrderExt workOrderExt = workOrderExtMap.get(workOrder.getWorkOrderId());
            if (workOrderExt == null) {
                continue;
            }
            JSONObject draftData = JSON.parseObject(workOrderExt.getDraftData());
            JSONObject masterData = draftData.getJSONObject(FlowConstant.MASTER_DATA_KEY);
            JSONObject slaveData = draftData.getJSONObject(FlowConstant.SLAVE_DATA_KEY);
            M model;
            if (masterData != null) {
                model = masterData.toJavaObject(modelClass);
                super.buildRelationForData(model, MyRelationParam.dictOnly());
            } else {
                model = ReflectUtil.newInstance(modelClass);
            }
            if (slaveData != null) {
                for (BaseService.RelationStruct relationStruct : super.localRelationOneToOneStructList) {
                    Field relationField = relationStruct.getRelationField();
                    JSONObject slaveObject = slaveData.getJSONObject(relationField.getName());
                    if (slaveObject != null) {
                        ReflectUtil.setFieldValue(model, relationField,
                                slaveObject.toJavaObject(relationStruct.getRelationOneToOne().slaveModelClass()));
                    }
                }
            }
            super.buildLocalOneToOneDictOnly(model);
            workOrder.setMasterData(BeanUtil.beanToMap(model));
        }
    }

    /**
     * 是否支持业务数据同步。每个子类需要根据实际情况判断是否需要支持。
     *
     * @return true支持，否则false。
     */
    public boolean supportSyncBusinessData() {
        return false;
    }

    /**
     * 是否支持流程状态更新到业务表字段。。
     *
     * @return true支持，否则false。
     */
    public boolean supportUpdateFlowStatus() {
        return false;
    }

    /**
     * 在流程实例审批结束后，需要进行审批表到发布表数据同步的服务实现子类，需要实现该方法。
     *
     * @param workOrder 工单对象。
     */
    public void syncBusinessData(FlowWorkOrder workOrder) {
        throw new UnsupportedOperationException();
    }

    /**
     * 在流程实例审批结束后，需要进行业务表的状态更新，业务Service需要实现该方法。
     *
     * @param workOrder 工单对象。
     */
    public void updateFlowStatus(FlowWorkOrder workOrder) {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取业务详情数据。
     *
     * @param processInstanceId 流程实例Id。
     * @param businessKey       业务主键Id。如果与实际主键值类型不同，需要在子类中自行完成类型转换。
     * @return 业务主表数据，以及关联从表数据。
     */
    @SuppressWarnings("unchecked")
    public String getBusinessData(String processInstanceId, String businessKey) {
        M data;
        if (idFieldClass.equals(Long.class)) {
            Long dataId = Long.valueOf(businessKey);
            data = this.getByIdWithRelation((K) dataId, MyRelationParam.full());
        } else if (idFieldClass.equals(Integer.class)) {
            Integer dataId = Integer.valueOf(businessKey);
            data = this.getByIdWithRelation((K) dataId, MyRelationParam.full());
        } else {
            data = this.getByIdWithRelation((K) businessKey, MyRelationParam.full());
        }
        return JSON.toJSONStringWithDateFormat(data, MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
    }

    /**
     * 规格化工单扩展表中保存的草稿数据。如果仅仅包含主表数据，可以使用当前的缺省实现。
     *
     * @param processInstanceId 流程实例Id。
     * @param masterData        草稿中的主表数据。
     * @param slaveData         草稿中的全部关联从表数据。
     * @return 格式化后用于前端显示的业务草稿数据。
     */
    public String getNormalizedDraftData(String processInstanceId, JSONObject masterData, JSONObject slaveData) {
        // 看到这个异常千万别慌，这是及时的提示您，如果草稿中包含了关联从表数据，就一定要在业务的ServiceImpl中实现该方法。
        // 在子类中实现主表和关联从表数据的数据组装。通用的方法中，只能解析主表数据，关联从表各式各样，所以需要在子类中实现。
        if (slaveData != null) {
            throw new UnsupportedOperationException(
                    "Please implement getNormalizedDraftData by self, because draft data includes related slaveData");
        }
        if (masterData == null) {
            return JSON.toJSONString(ReflectUtil.newInstance(modelClass));
        }
        M model = BeanUtil.toBean(masterData, modelClass);
        // 这里会缺省缓存主表中上传字段的下载地址，以便在显示草稿数据时，这些上传字段对应的download方法，不会被数据权限过滤。
        Set<String> cachedDownableFilename =
                new HashSet<>(MyModelUtil.extractDownloadFileName(model, modelClass));
        cacheHelper.putSessionDownloadableFileNameSet(cachedDownableFilename);
        return JSON.toJSONStringWithDateFormat(model, MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
    }
}
