package cn.stylefeng.workflow.instance.modular.manage.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.roses.kernel.db.api.factory.PageFactory;
import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.workflow.api.entity.instance.RuntimeNode;
import cn.stylefeng.workflow.api.entity.instance.RuntimeNodeEdge;
import cn.stylefeng.workflow.api.enums.NodeStatusEnum;
import cn.stylefeng.workflow.api.enums.NodeTypeEnum;
import cn.stylefeng.workflow.instance.modular.manage.enums.RuntimeNodeExceptionEnum;
import cn.stylefeng.workflow.instance.modular.manage.mapper.RuntimeNodeMapper;
import cn.stylefeng.workflow.instance.modular.manage.pojo.request.RuntimeNodeRequest;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeNodeEdgeService;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeNodeService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class RuntimeNodeServiceImpl extends ServiceImpl<RuntimeNodeMapper, RuntimeNode> implements RuntimeNodeService {

    @Resource
    private RuntimeNodeEdgeService runtimeNodeEdgeService;

    @Override
    public void add(RuntimeNodeRequest runtimeNodeRequest) {
        RuntimeNode runtimeNode = new RuntimeNode();
        BeanUtil.copyProperties(runtimeNodeRequest, runtimeNode);
        this.save(runtimeNode);
    }

    @Override
    public void del(RuntimeNodeRequest runtimeNodeRequest) {
        RuntimeNode runtimeNode = this.queryRuntimeNode(runtimeNodeRequest);
        this.removeById(runtimeNode.getRuNodeId());
    }

    @Override
    public void edit(RuntimeNodeRequest runtimeNodeRequest) {
        RuntimeNode runtimeNode = this.queryRuntimeNode(runtimeNodeRequest);
        BeanUtil.copyProperties(runtimeNodeRequest, runtimeNode);
        this.updateById(runtimeNode);
    }

    @Override
    public RuntimeNode detail(RuntimeNodeRequest runtimeNodeRequest) {
        return this.queryRuntimeNode(runtimeNodeRequest);
    }

    @Override
    public PageResult<RuntimeNode> findPage(RuntimeNodeRequest runtimeNodeRequest) {
        LambdaQueryWrapper<RuntimeNode> wrapper = createWrapper(runtimeNodeRequest);
        Page<RuntimeNode> sysRolePage = this.page(PageFactory.defaultPage(), wrapper);
        return PageResultFactory.createPageResult(sysRolePage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cleanRuntimeNodeAndEdges(Long instanceId, Long runtimeNodeId) {

        LambdaQueryWrapper<RuntimeNode> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.gt(RuntimeNode::getRuNodeId, runtimeNodeId);
        queryWrapper.eq(RuntimeNode::getInstanceId, instanceId);
        queryWrapper.select(RuntimeNode::getRuNodeId);
        List<RuntimeNode> afterRutimeNodeList = this.list(queryWrapper);

        List<Long> ruNodeIdList = afterRutimeNodeList.stream().map(RuntimeNode::getRuNodeId).collect(Collectors.toList());
        if (ObjectUtil.isNotEmpty(ruNodeIdList)) {

            this.removeBatchByIds(ruNodeIdList);

            LambdaUpdateWrapper<RuntimeNodeEdge> deleteWrapper = new LambdaUpdateWrapper<>();
            deleteWrapper.in(RuntimeNodeEdge::getSourceNodeId, ruNodeIdList);
            deleteWrapper.or().in(RuntimeNodeEdge::getTargetNodeId, ruNodeIdList);
            this.runtimeNodeEdgeService.remove(deleteWrapper);
        }

    }

    @Override
    public List<RuntimeNode> getInstanceFinishedNodeList(RuntimeNodeRequest runtimeNodeRequest) {
        LambdaQueryWrapper<RuntimeNode> wrapper = this.createWrapper(runtimeNodeRequest);
        wrapper.select(RuntimeNode::getNodeId, RuntimeNode::getNodeName);
        return this.list(wrapper);
    }

    @Override
    public RuntimeNode getInstanceRuntimeNodeByNodeId(Long instanceId, Long nodeId) {

        LambdaQueryWrapper<RuntimeNode> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(RuntimeNode::getInstanceId, instanceId);

        queryWrapper.eq(RuntimeNode::getNodeId, nodeId);

        queryWrapper.select(RuntimeNode::getRuNodeId, RuntimeNode::getNodeStatus, RuntimeNode::getBackType,
                RuntimeNode::getInstanceId, RuntimeNode::getNodeId);

        return this.getOne(queryWrapper);
    }

    @Override
    public List<RuntimeNode> findList(RuntimeNodeRequest runtimeNodeRequest) {
        LambdaQueryWrapper<RuntimeNode> wrapper = this.createWrapper(runtimeNodeRequest);
        return this.list(wrapper);
    }

    @Override
    public RuntimeNode getRuntimeNodeById(Long runtimeNodeId) {
        RuntimeNodeRequest runtimeNodeRequest = new RuntimeNodeRequest();
        runtimeNodeRequest.setRuNodeId(runtimeNodeId);
        return this.queryRuntimeNode(runtimeNodeRequest);
    }

    @Override
    public RuntimeNode saveRuntimeNode(RuntimeNode runtimeNode) {
        if (ObjectUtil.isNotEmpty(runtimeNode)) {
            this.save(runtimeNode);
            return runtimeNode;
        }
        return null;
    }

    @Override
    public RuntimeNode getStartRuntimeNode(Long instanceId) {
        LambdaQueryWrapper<RuntimeNode> runtimeNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getInstanceId, instanceId);
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getNodeType, NodeTypeEnum.APPLICANT.getNodeType());
        return this.getOne(runtimeNodeLambdaQueryWrapper);
    }

    @Override
    public void updateRuntimeNode(RuntimeNode runtimeNode) {
        this.updateById(runtimeNode);
    }

    @Override
    public void saveOrUpdateRuntimeNode(RuntimeNode runtimeNode) {

        LambdaQueryWrapper<RuntimeNode> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RuntimeNode::getInstanceId, runtimeNode.getInstanceId());
        queryWrapper.eq(RuntimeNode::getNodeId, runtimeNode.getNodeId());
        RuntimeNode oldRuntimeNode = this.getOne(queryWrapper);

        if (oldRuntimeNode != null) {

            oldRuntimeNode.setNodeStatus(runtimeNode.getNodeStatus());
            oldRuntimeNode.setBackType(runtimeNode.getBackType());
            this.updateById(oldRuntimeNode);
            return;
        }

        this.save(runtimeNode);
    }

    @Override
    public List<RuntimeNode> getAlreadyFinishedRuntimeNode(Long instanceId, Long runtimeNodeId) {
        if (ObjectUtil.isEmpty(runtimeNodeId)) {
            return new ArrayList<>();
        }

        LambdaQueryWrapper<RuntimeNode> runtimeNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getInstanceId, instanceId);
        runtimeNodeLambdaQueryWrapper.lt(RuntimeNode::getRuNodeId, runtimeNodeId);
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getNodeStatus, NodeStatusEnum.FINISH.getStatus());

        runtimeNodeLambdaQueryWrapper.select(RuntimeNode::getRuNodeId, RuntimeNode::getNodeId, RuntimeNode::getNodeType, RuntimeNode::getNodeName);

        return this.list(runtimeNodeLambdaQueryWrapper);
    }

    @Override
    public RuntimeNode getRuntimeNodeByNodeId(Long instanceId, Long nodeId) {
        LambdaQueryWrapper<RuntimeNode> runtimeNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getInstanceId, instanceId);
        runtimeNodeLambdaQueryWrapper.eq(RuntimeNode::getNodeId, nodeId);
        return this.getOne(runtimeNodeLambdaQueryWrapper, false);
    }

    @Override
    public void closeInstanceRuntimeNodes(Long instanceId) {
        LambdaUpdateWrapper<RuntimeNode> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(RuntimeNode::getInstanceId, instanceId);
        wrapper.set(RuntimeNode::getNodeStatus, NodeStatusEnum.REJECT.getStatus());
        this.update(wrapper);
    }

    private RuntimeNode queryRuntimeNode(RuntimeNodeRequest runtimeNodeRequest) {
        RuntimeNode runtimeNode = this.getById(runtimeNodeRequest.getRuNodeId());
        if (ObjectUtil.isEmpty(runtimeNode)) {
            throw new ServiceException(RuntimeNodeExceptionEnum.RUNTIME_NODE_NOT_EXISTED);
        }
        return runtimeNode;
    }

    private LambdaQueryWrapper<RuntimeNode> createWrapper(RuntimeNodeRequest runtimeNodeRequest) {
        LambdaQueryWrapper<RuntimeNode> queryWrapper = new LambdaQueryWrapper<>();

        Long instanceId = runtimeNodeRequest.getInstanceId();
        queryWrapper.eq(ObjectUtil.isNotNull(instanceId), RuntimeNode::getInstanceId, instanceId);

        List<Long> instanceIds = runtimeNodeRequest.getInstanceIdList();
        queryWrapper.in(ObjectUtil.isNotEmpty(instanceIds), RuntimeNode::getInstanceId, instanceIds);

        Long nodeId = runtimeNodeRequest.getNodeId();
        queryWrapper.eq(ObjectUtil.isNotNull(nodeId), RuntimeNode::getNodeId, nodeId);

        Integer nodeStatus = runtimeNodeRequest.getNodeStatus();
        queryWrapper.eq(ObjectUtil.isNotNull(nodeStatus), RuntimeNode::getNodeStatus, nodeStatus);

        queryWrapper.orderByDesc(RuntimeNode::getRuNodeId);

        return queryWrapper;
    }
}