package com.sdtool.business.service;

import com.sdtool.business.dao.SystemDao;
import com.sdtool.common.api.DeptService;
import com.sdtool.common.api.LoginUserService;
import com.sdtool.common.api.NoSqlKeysService;
import com.sdtool.common.api.SystemService;
import com.sdtool.common.base.BaseService;
import com.sdtool.common.datas.ERPModule;
import com.sdtool.common.datas.ESKeys;
import com.sdtool.common.datas.HttpCode;
import com.sdtool.common.datas.RedisKeys;
import com.sdtool.common.entity.base.*;
import com.sdtool.common.entity.site.*;
import com.sdtool.common.entity.system.*;
import com.sdtool.common.tool.ERPUtils;
import com.dySweetFishPlugin.elasticsearch.ESClient;
import com.dySweetFishPlugin.redis.RedisService;
import com.dySweetFishPlugin.sql.RMapUtils;
import com.dySweetFishPlugin.sql.TableIdService;
import com.dySweetFishPlugin.sql.dao.TunaService;
import com.dySweetFishPlugin.tool.lang.ThreadFactoryWithNamePrefix;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.net.http.WebSocketNode;
import com.sweetfish.net.http.WebSocketRange;
import com.sweetfish.service.Local;
import com.sweetfish.service.RetResult;
import com.sweetfish.source.PageFlipper;
import com.sweetfish.util.AnyValue;
import com.sweetfish.util.AutoLoad;
import com.sweetfish.util.ResourceFactory;
import com.sweetfish.util.ResourceType;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.rex.RMap;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * Created by jlutt on 2020-07-29
 *
 * @author jlutt
 */
@AutoLoad(false)
@Local
@ResourceType(SystemService.class)
@SuppressWarnings("rawtypes")
public class SystemServiceImpl extends BaseService implements SystemService {

    @Resource(name = "APP_HOME")
    String homePath;

    @Resource
    JsonConvert jsonConvert;

    @Resource
    TunaService tunaService;

    @Resource
    RedisService redisService;

    @Resource
    NoSqlKeysService keysService;

    @Resource
    TableIdService tableIdService;

    @Resource
    ESClient esClient;

    @Resource
    LoginUserService userService;

    @Resource
    DeptService deptService;

    private SystemDao systemDao;

    private ExecutorService exportExecutor;

    private ExecutorService initExecutor;


    @Resource(name = "property.webSocketServerName")
    private String webSocketServerName;

    private String formTokenScriptHash;

    /**
     * 该方法必须是可以重复调用， 当reload时需要重复调用init方法
     * 远程模式下该方法会重载成空方法
     *
     * @param config 配置参数
     */
    @Override
    public void init(AnyValue config) {
        super.init(config);
        exportExecutor = Executors.newFixedThreadPool(8, new ThreadFactoryWithNamePrefix("[导出线程池]"));
        initExecutor = Executors.newFixedThreadPool(2, new ThreadFactoryWithNamePrefix("[初始化Saas客户线程池]"));
    }

    /**
     * 进程退出时，调用Service销毁
     * 远程模式下该方法会重载成空方法
     *
     * @param config 配置参数
     */
    @Override
    public void destroy(AnyValue config) {
        super.init(config);

        if (exportExecutor != null) {
            exportExecutor.shutdown();
        }

        if (initExecutor != null) {
            initExecutor.shutdown();
        }
    }

    /**
     * 在Server启动的时候调用，只有本地service会调用
     *
     * @param config 配置参数
     */
    @Override
    public void start(AnyValue config) {
        systemDao = tunaService.generate(SystemDao.class);

        Path path = Paths.get(homePath + File.separator + "conf" + File.separator + "redisscript" + File.separator + "formtoken.lua");
        this.formTokenScriptHash = redisService.scriptLoad(readUTF8File(path, false));
    }

    //region ERP文件

    /**
     * 保存上传的erp文件
     *
     * @param uploadFile   文件信息
     * @param currentUser  操作人
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 操作信息
     */
    @Override
    public int uploadERPFile(ERPUploadFile uploadFile, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        ERPUploadFile.create(uploadFile, currentUser.getId());

        //写es
        esClient.getClient().prepareIndex(keysService.getESKey(ESKeys.ESERPFILE_INDEX, supplierCode), ESKeys.ESERPDEFAULT_TYPE)
                .setSource(jsonConvert.convertTo(uploadFile), XContentType.JSON)
                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
                .get();

        //写数据库
        systemDao.insertERPFile(uploadFile, dataSourceId, supplierCode);

        return 1;
    }

    /**
     * 文件查询
     *
     * @param searchData  查询条件
     * @param currentUser 操作人
     * @param pageFlipper 分页信息
     * @param orderBy     排序
     * @return 文件列表
     */
    @Override
    @SuppressWarnings({"rawtypes"})
    public RetResult<List<ERPUploadFile>> getERPFileList(RMap searchData,
                                                         ERPTokenUser currentUser,
                                                         PageFlipper pageFlipper,
                                                         String orderBy,
                                                         String dataSourceId,
                                                         long supplierCode) {
        if (currentUser == null) {
            return RetResult.<List<ERPUploadFile>>errorT().retinfo("未登录");
        }
        //从es中获取

        BoolQueryBuilder qb = QueryBuilders.boolQuery();

        if (ERPUtils.mapContainKey(searchData, "fileKind")) {
            String fileKind = searchData.getString("fileKind");
            if (!"-1".equalsIgnoreCase(fileKind)) {
                qb = qb.must(QueryBuilders.termQuery("fileKind", Integer.parseInt(fileKind)));
            }
        }

        if (ERPUtils.mapContainKey(searchData, "keyWord")) {
            String keyWord = searchData.getString("keyWord");
            BoolQueryBuilder qbKeyWord = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("srcFileName", keyWord).analyzer("query_ansj").operator(Operator.AND))
                    .should(QueryBuilders.matchQuery("fileDescription", keyWord).analyzer("query_ansj").operator(Operator.AND));
            qb = qb.must(qbKeyWord);
        }

        SortBuilder sortBuilder;
        if (StringUtils.isEmpty(orderBy)) {
            sortBuilder = SortBuilders.fieldSort("createTimeLong").order(SortOrder.DESC);
        } else {
            String orderByField = orderBy.split(" ")[0];
            String orderBySort = orderBy.split(" ")[1];
            if ("code".equalsIgnoreCase(orderByField)) {
                sortBuilder = SortBuilders.fieldSort(orderByField);
            } else {
                sortBuilder = SortBuilders.fieldSort(orderByField + ".raw");
            }
            if ("asc".equalsIgnoreCase(orderBySort)) {
                sortBuilder = sortBuilder.order(SortOrder.ASC);
            } else if ("desc".equalsIgnoreCase(orderBySort)) {
                sortBuilder = sortBuilder.order(SortOrder.DESC);
            }
        }
        SearchRequestBuilder requestBuilder = esClient.getClient().prepareSearch(keysService.getESKey(ESKeys.ESERPFILE_INDEX, supplierCode))
                .setTypes(ESKeys.ESERPDEFAULT_TYPE)
                .setQuery(qb)
                .addSort(sortBuilder)
                .setSize(pageFlipper.getPageSize())
                .setFrom(pageFlipper.getPageSize() * (pageFlipper.getPageIndex() - 1));

        SearchHits searchHits = requestBuilder.execute().actionGet().getHits();

        List<ERPUploadFile> esList = new ArrayList<>();
        searchHits.forEach(x -> {
            String json = x.getSourceAsString();
            ERPUploadFile erpFile = jsonConvert.convertFrom(ERPUploadFile.class, json);

            esList.add(erpFile);
        });
        pageFlipper.setTotal(searchHits.getTotalHits());

        return RetResult.<List<ERPUploadFile>>successT().result(esList).page(pageFlipper);

    }

    //endregion

    //region 消息类型

    private NoticeMessage getRedisNoticeMessage(long messageId, long supplierCode) {
        String json = redisService.hget(keysService.getRedisKey(RedisKeys.KEY_ERP_NOTICEMESSAGE, supplierCode), String.valueOf(messageId));

        if (StringUtils.isEmpty(json)) {
            return null;
        }

        return jsonConvert.convertFrom(NoticeMessage.class, json);
    }

    /**
     * 获取消息类型列表
     * 从redis中获取
     *
     * @param searchData 查询条件
     * @return 消息类型
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RetResult<List<NoticeMessage>> getNoticeMessageList(RMap searchData, String dataSourceId, long supplierCode) {
        String searchName = "";
        int searchStatus = -1;

        if (ERPUtils.mapContainKey(searchData, "keyWord")) {
            searchName = searchData.getString("keyWord");
        }

        if ((searchData != null) && (searchData.containsKey("status"))) {
            searchStatus = searchData.getInt("status");
        }

        Map<String, String> noticeMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_ERP_NOTICEMESSAGE, supplierCode));
        List<NoticeMessage> noticeList = new ArrayList<>();

        final String finalName = searchName;
        final int finalStatus = searchStatus;

        noticeMap.forEach((k, v) -> {
            NoticeMessage noticeMessage = jsonConvert.convertFrom(NoticeMessage.class, v);
            boolean canAdd = true;

            if (StringUtils.isNotEmpty(finalName)) {
                canAdd = noticeMessage.getMessageName().contains(finalName);
            }

            if (finalStatus >= 0) {
                canAdd = canAdd && (noticeMessage.getVoidFlag() == finalStatus);
            }

            if (canAdd) {
                noticeList.add(noticeMessage);
            }
        });

        return RetResult.<List<NoticeMessage>>successT().result(noticeList);

    }

    /**
     * 增加消息类型
     *
     * @param noticeMessage 消息类型
     * @param currentUser   操作人
     * @return 操作信息
     */
    @Override
    public RetResult<NoticeMessage> addNoticeMessage(NoticeMessage noticeMessage, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        noticeMessage.setId(tableIdService.getTableIdMulti("tbNoticeMessage.id", 1, dataSourceId, String.valueOf(supplierCode)));
        noticeMessage.setVoidFlag(0);

        NoticeMessage.create(noticeMessage, currentUser.getId());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_ERP_NOTICEMESSAGE, supplierCode), String.valueOf(noticeMessage.getId()), jsonConvert.convertTo(noticeMessage));

        systemDao.addNoticeMessage(noticeMessage, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    @Override
    public RetResult<NoticeMessage> getNoticeMessage(long messageId, String dataSourceId, long supplierCode) {
        NoticeMessage noticeMessage = getRedisNoticeMessage(messageId, supplierCode);
        return RetResult.<NoticeMessage>successT().result(noticeMessage);
    }

    /**
     * 修改消息类型
     *
     * @param noticeMessage 消息类型
     * @param currentUser   操作人
     * @return 操作信息
     */
    @Override
    public RetResult<NoticeMessage> updateNoticeMessage(NoticeMessage noticeMessage, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (noticeMessage == null) {
            return RetResult.<NoticeMessage>errorT().retinfo("请求数据错误");
        }

        if (noticeMessage.getId() == 0L) {
            return RetResult.<NoticeMessage>errorT().retinfo("请求数据错误");
        }

        NoticeMessage.update(noticeMessage, currentUser.getId());

        NoticeMessage redisInfo = getRedisNoticeMessage(noticeMessage.getId(), supplierCode);
        if (redisInfo != null) {
            redisInfo.setMessageKind(noticeMessage.getMessageKind());
            redisInfo.setMessageName(noticeMessage.getMessageName());
            redisInfo.setUpdateBy(noticeMessage.getUpdateBy());
            redisInfo.setUpdateTime(noticeMessage.getUpdateTime());


            redisService.hset(keysService.getRedisKey(RedisKeys.KEY_ERP_NOTICEMESSAGE, supplierCode), String.valueOf(redisInfo.getId()), jsonConvert.convertTo(redisInfo));
        }

        systemDao.updateNoticeMessage(noticeMessage, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 切换消息类型状态
     *
     * @param noticeMessage 消息类型
     * @param currentUser   操作人
     * @return 操作信息
     */
    @Override
    public RetResult<NoticeMessage> voidNoticeMessage(NoticeMessage noticeMessage, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        NoticeMessage.update(noticeMessage, currentUser.getId());

        NoticeMessage redisInfo = getRedisNoticeMessage(noticeMessage.getId(), supplierCode);
        if (redisInfo != null) {
            redisInfo.setVoidFlag(noticeMessage.getVoidFlag());
            redisInfo.setUpdateBy(noticeMessage.getUpdateBy());
            redisInfo.setUpdateTime(noticeMessage.getUpdateTime());

            redisService.hset(keysService.getRedisKey(RedisKeys.KEY_ERP_NOTICEMESSAGE, supplierCode), String.valueOf(redisInfo.getId()), jsonConvert.convertTo(redisInfo));
        }

        systemDao.voidNoticeMessage(noticeMessage, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 获取消息订阅用户列表
     * 配置使用
     *
     * @param idNoticeMessage 消息类型id
     * @param dataSourceId    分库
     * @param supplierCode    分表
     * @return 订阅用户列表
     */
    public List<UserNoticeMessage> getNoticeMessageUserList(long idNoticeMessage, String dataSourceId, long supplierCode) {
        NoticeMessage noticeMessage = getRedisNoticeMessage(idNoticeMessage, supplierCode);
        if (noticeMessage == null) {
            return null;
        }

        Map<String, String> redisMap = redisService.hscan(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), noticeMessage.getMessageCode() + "-*-0");

        return redisMap.keySet().stream()
                .map(s -> {
                    String[] keys = s.split("-");
                    UserNoticeMessage user = new UserNoticeMessage();
                    user.setId(0L);
                    user.setIdUser(Long.parseLong(keys[1]));
                    user.setIdNoticeMessage(idNoticeMessage);

                    LoginUser loginUser = userService.getRedisLoginUser(user.getIdUser(), supplierCode);
                    if (loginUser != null) {
                        user.setUserName(loginUser.getUserName());
                        user.setDeptName(deptService.getRedisDeptName(loginUser.getDeptId(), supplierCode));
                    }

                    return user;
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取消息订阅用户 socket id列表
     * 发送消息使用
     *
     * @param messageCode  消息编号
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 订阅用户 socket id列表
     */
    public List<String> getNoticeMessageUser(String messageCode, String dataSourceId, long supplierCode) {
        Map<String, String> redisMap = redisService.hscan(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), messageCode + "-*-0");

        return redisMap.keySet().stream()
                .map(s -> {
                    String[] keys = s.split("-");
                    return keys[1] + "_" + supplierCode + "_" + dataSourceId;
                })
                .collect(Collectors.toList());
    }

    /**
     * 指定消息类型增加订阅用户
     *
     * @param userNoticeMessage 用户订阅消息
     * @param currentUser       操作人
     * @param dataSourceId      分库
     * @param supplierCode      分表
     * @return 操作信息
     */
    public RetResult<NoticeMessage> addUserNoticeMessage(UserNoticeMessage userNoticeMessage, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {

        if (currentUser == null) {
            return RetResult.<NoticeMessage>errorT().retinfo("未登录");
        }

        if (userNoticeMessage.getIdUser() <= 0L) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择用户");
        }

        if (userNoticeMessage.getIdNoticeMessage() <= 0L) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择消息类型");
        }

        NoticeMessage noticeMessage = getRedisNoticeMessage(userNoticeMessage.getIdNoticeMessage(), supplierCode);
        if (noticeMessage == null) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择消息类型");
        }

        if (redisService.hexists(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), noticeMessage.getMessageCode() + "-" + userNoticeMessage.getIdUser() + "-0")) {
            return RetResult.<NoticeMessage>errorT().retinfo("订阅用户已经存在");
        }

        userNoticeMessage.setId(tableIdService.getTableIdMulti("tbUserNoticeMessage.id", 1, dataSourceId, String.valueOf(supplierCode)));

        UserNoticeMessage.create(userNoticeMessage, currentUser.getId());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), noticeMessage.getMessageCode() + "-" + userNoticeMessage.getIdUser() + "-0", "1");

        systemDao.addUserNoticeMessage(userNoticeMessage, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 指定消息类型删除订阅用户
     *
     * @param userNoticeMessage 用户订阅消息
     * @param dataSourceId      分库
     * @param supplierCode      分表
     * @return 操作信息
     */
    public RetResult<NoticeMessage> removeUserNoticeMessage(UserNoticeMessage userNoticeMessage, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {

        if (currentUser == null) {
            return RetResult.<NoticeMessage>errorT().retinfo("未登录");
        }

        if (userNoticeMessage.getIdUser() <= 0L) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择用户");
        }

        if (userNoticeMessage.getIdNoticeMessage() <= 0L) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择消息类型");
        }

        NoticeMessage noticeMessage = getRedisNoticeMessage(userNoticeMessage.getIdNoticeMessage(), supplierCode);
        if (noticeMessage == null) {
            return RetResult.<NoticeMessage>errorT().retinfo("未选择消息类型");
        }

        if (!redisService.hexists(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), noticeMessage.getMessageCode() + "-" + userNoticeMessage.getIdUser() + "-0")) {
            return RetResult.<NoticeMessage>errorT().retinfo("订阅用户不存在");
        }

        redisService.hdel(keysService.getRedisKey(RedisKeys.KEY_ERP_USERNOTICEMESSAGE, supplierCode), noticeMessage.getMessageCode() + "-" + userNoticeMessage.getIdUser() + "-0");

        systemDao.deleteNoticeMessage(userNoticeMessage, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    //endregion

    //region 通知消息

    /**
     * 写入在线用户
     *
     * @param userInfo        用户信息
     * @param socketSessionId websocket传入的sessionid
     * @param dataSourceId    分库
     * @param supplierCode    分表
     */
    public void writeOnlineUser(LoginUser userInfo, String socketSessionId, String dataSourceId, long supplierCode) {
        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_ERP_ONLINEUSER, supplierCode), userInfo.getId() + "_" + socketSessionId, userInfo);
    }

    /**
     * 删除在线用户
     *
     * @param userId          用户id
     * @param socketSessionId sessionId
     * @param dataSourceId    分库
     * @param supplierCode    分表
     */
    public void deleteOnlineUser(long userId, String socketSessionId, String dataSourceId, long supplierCode) {
        redisService.hdel(keysService.getRedisKey(RedisKeys.KEY_ERP_ONLINEUSER, supplierCode), userId + "_" + socketSessionId);
//        imageDispatchService.clockOut(userId, supplierCode);
    }

    /**
     * 获取在线用户列表
     *
     * @return 在线用户列表
     */
    public List<LoginUser> queryOnlineUser(String dataSourceId, long supplierCode) {
        Map<String, String> userJsonMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_ERP_ONLINEUSER, supplierCode));
        return userJsonMap.values().stream()
                .map((x) -> {
                    LoginUser loginUser = jsonConvert.convertFromO(LoginUser.class, x);
                    loginUser.setDeptName(deptService.getRedisDeptName(loginUser.getDeptId(), supplierCode));
                    return loginUser;
                })
                .toList().stream()
                .filter(ERPUtils.distinctByKey(LoginUser::getId))
                .collect(Collectors.toList());
    }

    /**
     * 订阅消息组
     *
     * @return 操作信息
     */
    @SuppressWarnings("unused")
    public int subscribeMessageGroup(ERPTokenUser currentUser, long supplierCode) {
        return -1;
    }

    @SuppressWarnings("unused")
    public int broadcastMessage() {
        return -1;
    }

    /**
     * 按照消息类型发送文本信息
     *
     * @param messageCode    消息类型，对应NoticeMessage.messageCode
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPNoticeMessage1(ERPTokenUser currentUser, String messageCode, String messageContent, String dataSourceId, long supplierCode) {
        WebSocketNode erpWebSocket = ResourceFactory.root().find(webSocketServerName, WebSocketNode.class);
        if (erpWebSocket != null) {

//            erpWebSocket.broadcastMessage(new ERPNoticeMessage(currentUser.getId(), currentUser.getUserName(), String.valueOf(ERPMessageKind.COMMAND), messageContent));

            //也可以通过下面的方式，用指定的连接发送信息。。但当前实现不了类似layim的框架，
            //无法找到发送人的websocket
//            erpWebSocket.getLocalWebSocketEngine().getLocalWebSockets()
            String[] userIds = getNoticeMessageUser(messageCode, dataSourceId, supplierCode)
                    .stream().filter((x) -> erpWebSocket.getLocalWebSocketEngine().existsLocalWebSocket(x)).toArray(String[]::new);
            if (userIds.length > 0) {
                erpWebSocket.sendMessage(new ERPNoticeMessage(currentUser.getId(), currentUser.getUserName(), String.valueOf(ERPMessageKind.COMMAND), messageContent), userIds);
            }
        }
    }

    /**
     * 按照消息类型发送文本信息
     *
     * @param messageCode    消息类型，对应NoticeMessage.messageCode
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPNoticeMessage2(String messageCode, String messageContent, String dataSourceId, long supplierCode) {
        sendERPNoticeMessage1(ERPUtils.getSysTokenUser(), messageCode, messageContent, dataSourceId, supplierCode);
    }

    /**
     * 按照用户ids发送文本信息
     *
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPNoticeMessage3(ERPTokenUser currentUser, String userIds, String messageContent, String dataSourceId, long supplierCode) {
        sendERPMessage(currentUser, userIds, messageContent, ERPMessageKind.COMMAND, dataSourceId, supplierCode);
    }

    /**
     * 按照用户ids发送文本信息
     *
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPNoticeMessage4(String userIds, String messageContent, String dataSourceId, long supplierCode) {
        sendERPNoticeMessage3(ERPUtils.getSysTokenUser(), userIds, messageContent, dataSourceId, supplierCode);
    }

    /**
     * 按照用户ids发送打印信息
     *
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPPrintMessage(ERPTokenUser currentUser, String userIds, String messageContent, String dataSourceId, long supplierCode) {
        sendERPMessage(currentUser, userIds, messageContent, ERPMessageKind.PRINT, dataSourceId, supplierCode);
    }

    /**
     * 按照用户ids发送打印信息
     *
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void sendERPPrintMessage2(String userIds, String messageContent, String dataSourceId, long supplierCode) {
        sendERPPrintMessage(ERPUtils.getSysTokenUser(), userIds, messageContent, dataSourceId, supplierCode);
    }

    private void sendERPMessage(ERPTokenUser currentUser, String userIds, String messageContent, int messageKind, String dataSourceId, long supplierCode) {
        WebSocketNode erpWebSocket = ResourceFactory.root().find(webSocketServerName, WebSocketNode.class);
        if (erpWebSocket != null) {
            String[] userIdArray = Arrays.stream(userIds.split(",")).map((x) -> x + "_" + supplierCode + "_" + dataSourceId)
                    .filter((x) -> erpWebSocket.getLocalWebSocketEngine().existsLocalWebSocket(x))
                    .toArray(String[]::new);
            if (userIdArray.length > 0) {

                erpWebSocket.sendMessage(new ERPNoticeMessage(currentUser.getId(), currentUser.getUserName(), String.valueOf(messageKind), messageContent), userIdArray);
            }
        }
    }

    public void sendShowERPMessage(ERPTokenUser currentUser, String userIds, String messageContent, String dataSourceId, long supplierCode) {
        sendERPMessage(currentUser, userIds, messageContent, ERPMessageKind.SHOW_MODAL_COMMAND, dataSourceId, supplierCode);
    }

    /**
     * 广播消息
     *
     * @param messageContent 消息内容
     * @param supplierCode   分表
     */
    public void broadcastMessage(String messageContent, String dataSourceId, long supplierCode) {
        WebSocketNode erpWebSocket = ResourceFactory.root().find(webSocketServerName, WebSocketNode.class);
        if (erpWebSocket != null) {
            erpWebSocket.broadcastMessage(new WebSocketRange("_" + supplierCode + "_" + dataSourceId),
                    new ERPNoticeMessage(0L, "系统", String.valueOf(ERPMessageKind.COMMAND), messageContent));
        }
    }

    //endregion

    //region 仪表盘面板

    /**
     * 获取仪表盘配置列表
     *
     * @param currentUser  当前用户
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 仪表盘列表
     */
    @SuppressWarnings({"rawtypes", "unused"})
    public RMap getCardPanelList(ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        Map<String, String> redisMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_ERPCARDPANEL, supplierCode));

        List<CardPanel> teamList = redisMap.values().stream()
                .map((x) -> (CardPanel) jsonConvert.convertFromO(CardPanel.class, x)).sorted(Comparator.comparingLong(CardPanel::getId))
                .collect(Collectors.toList());

        return RMapUtils.successV2(teamList, null, null);
    }

    public CardPanel getRedisCardPanelInfo(long id, long supplierCode) {
        String json = redisService.hget(keysService.getRedisKey(RedisKeys.KEY_ERPCARDPANEL, supplierCode), String.valueOf(id));

        if (StringUtils.isEmpty(json)) {
            return null;
        }

        return jsonConvert.convertFrom(CardPanel.class, json);
    }

    @SuppressWarnings({"rawtypes", "unused"})
    public RMap getCardPanelInfo(long id, String dataSourceId, long supplierCode) {
        CardPanel cardPanel = getRedisCardPanelInfo(id, supplierCode);
        if (cardPanel == null) {
            return RMapUtils.error(HttpCode.NOT_FOUND.value(), "无效的数据信息");
        }

        return RMapUtils.successV2(cardPanel, null, null);
    }

    /**
     * 首页面板数据
     *
     * @param currentUser  操作用户
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 查询数据
     */
    @SuppressWarnings("rawtypes")
    public RMap getIndexCardPanelData(ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RMapUtils.error(HttpCode.UNAUTHORIZED.value(), "未登录");
        }

        RetResult<ProcessMapItem> execResult = handleScript("IndexCardPanelData", ERPModule.SYSTEM,
                dataSourceId, supplierCode,
                () -> ProcessMapItem.newBuilder()
                        .itemData(null)
                        .currentUser(currentUser)
                        .dataSourceId(dataSourceId)
                        .supplierCode(supplierCode)
                        .build()
        );
        if (execResult.isSuccess()) {
            return RMapUtils.successV2(execResult.getResult().getItemData(), null, null);
        }
        return RMapUtils.error(HttpCode.BAD_REQUEST.value(), execResult.getRetinfo());
    }

    //endregion

    //region 表单处理

    /**
     * 表单重复提交检查
     *
     * @param formName      表单名称
     * @param userSessionId 操作人
     * @return 是否重提提交
     */
    public boolean checkFormSubmitCount(String formName, String userSessionId, String dataSourceId, long supplierCode) {
        String redisKey = keysService.getRedisKey(RedisKeys.KEY_FORMTOKEN, supplierCode, true) +
                userSessionId + ":" +
                formName;

        long count = redisService.incrby(redisKey, 1);
        if (count == 1) {
            redisService.expire(redisKey, 5);
        }

        return count > 1;
    }

    /**
     * 表单重复提交检查
     *
     * @param formName      表单名称
     * @param expireSecond  过期时间
     * @param userSessionId 操作人
     * @return 是否重提提交
     */
    public boolean checkFormSubmitCount(String formName, int expireSecond, String userSessionId, String dataSourceId, long supplierCode) {
        String redisKey = keysService.getRedisKey(RedisKeys.KEY_FORMTOKEN, supplierCode, true) +
                userSessionId + ":" +
                formName;

        long count = redisService.incrby(redisKey, 1);
        if (count == 1) {
            redisService.expire(redisKey, expireSecond);
        }

        return count > 1;
    }

    /**
     * 请求formtoken
     *
     * @param formName     表单名称
     * @param supplierCode 分表
     * @return token
     */
    public String requestFormToken(String formName, long supplierCode) {
        String formToken = formName + "_" + UUID.randomUUID().toString().replace("-", "");

        redisService.hincrBy(keysService.getRedisKey(RedisKeys.KEY_FORMTOKENEX, supplierCode), formToken, 1);

        return formToken;
    }

    /**
     * 检查表达是否提交
     *
     * @param formToken    token
     * @param supplierCode 分表
     * @return -1已提交，1正常
     */
    public int checkFormToken(String formToken, long supplierCode) {
        String iValueStr = (String) (redisService.evalsha(this.formTokenScriptHash,
                1,
                keysService.getRedisKey(RedisKeys.KEY_FORMTOKENEX, supplierCode),
                formToken));

        long iValue = Long.parseLong(iValueStr);// redisService.hincrBy(keysService.getRedisKey(RedisKeys.KEY_FORMTOKENEX, supplierCode), formToken, 1);
        if (iValue == -1) {
            return -2;
        }

        if (iValue > 2) {
            //token已经执行过了
            return -1;
        }
        return 1;
    }

    /**
     * 删除token
     *
     * @param formToken    token
     * @param supplierCode 分表
     */
    public void resetFormToken(String formToken, long supplierCode) {
        redisService.hdel(keysService.getRedisKey(RedisKeys.KEY_FORMTOKENEX, supplierCode), formToken);
    }

    //endregion

    //region 调试
    @Override
    public RetResult updateEsData(String index, String type, String recordId, List<KeyValuePair> valuePairs) {
        Map<String, String> params = new HashMap<>();
        valuePairs.forEach(x -> params.put(x.getKey(), x.getValue()));


        try (XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()) {
            xContentBuilder.map(params);
            UpdateRequest updateRequest = new UpdateRequest(index, type, recordId)
                    .doc(xContentBuilder)
                    .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
            esClient.getClient().update(updateRequest).get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            logger.error("调试修改es出错: " + e.getMessage(), e);
            return RetResult.error().retcode(-1).retinfo("调试修改es出错: " + e.getMessage());
        }

        return RetResult.success();
    }
    //endregion

    //region 通用导出

    /**
     * 导出操作
     *
     * @param exportCode   脚本编号
     * @param exportName   导出名称
     * @param params       查询条件
     * @param currentUser  操作用户
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 操作信息
     */
    @SuppressWarnings("rawtypes")
    public RetResult<String> exportData(String exportCode, String exportName, RMap params, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        CompletableFuture.runAsync(() -> {
            RetResult<TaskQueue> execResult = handleScript(exportCode, ERPModule.EXPORT, dataSourceId, supplierCode,
                    () -> ProcessMapItem.newBuilder()
                            .itemData(params)
                            .currentUser(currentUser)
                            .dataSourceId(dataSourceId)
                            .supplierCode(supplierCode)
                            .build()
            );
            if (execResult.isSuccess()) {
                TaskQueue exportQueue = execResult.getResult();
                if (StringUtils.isNotBlank(exportName)) {
                    exportQueue.setTaskName(exportName);
                }
                exportQueue.setUserId(currentUser.getId());
                if ((params != null) && (params.containsKey("startTime"))) {
                    exportQueue.setQueryBeginDate(params.getString("beginTime"));
                }
                if ((params != null) && (params.containsKey("beginTime"))) {
                    exportQueue.setQueryBeginDate(params.getString("beginTime"));
                }
                if ((params != null) && (params.containsKey("endTime"))) {
                    exportQueue.setQueryEndDate(params.getString("endTime"));
                }
                exportQueue.setQueryContent("");
                TaskQueue.create(exportQueue, currentUser.getId());
                systemDao.saveTaskQueue(exportQueue, dataSourceId, supplierCode);
            } else {
                TaskQueue exportQueue = new TaskQueue();
                if (StringUtils.isNotBlank(exportName)) {
                    exportQueue.setTaskName(exportName);
                }
                exportQueue.setUserId(currentUser.getId());
                if ((params != null) && (params.containsKey("startTime"))) {
                    exportQueue.setQueryBeginDate(params.getString("beginTime"));
                }
                if ((params != null) && (params.containsKey("beginTime"))) {
                    exportQueue.setQueryBeginDate(params.getString("beginTime"));
                }
                if ((params != null) && (params.containsKey("endTime"))) {
                    exportQueue.setQueryEndDate(params.getString("endTime"));
                }
                exportQueue.setQueryContent("");
                exportQueue.setFileName("");
                exportQueue.setErrorMsg(execResult.getRetinfo());
                exportQueue.setFileURL("");
                exportQueue.setWebFileURL("");
                TaskQueue.create(exportQueue, currentUser.getId());
                systemDao.saveTaskQueue(exportQueue, dataSourceId, supplierCode);
            }
        }, exportExecutor);
        return RetResult.successT();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public RetResult<List<TaskQueue>> selectUserTaskQueues(RMap searchData, ERPTokenUser currentUser, PageFlipper pageFlipper, String dataSourceId, long supplierCode) {
        if (currentUser == null) {
            return RetResult.<List<TaskQueue>>errorT().retinfo("未登录");
        }

        RMap params = new RMap();

        if (searchData != null) {
            params.putAll(searchData);
        }

        params.put("userId", currentUser.getId());

        List<TaskQueue> taskList = systemDao.selectTaskQueue(params, pageFlipper, dataSourceId, supplierCode);

        return RetResult.<List<TaskQueue>>successT().result(taskList).page(pageFlipper);
    }

    //endregion

    //region 图形统计

    /**
     * 获取图表统计数据
     *
     * @param chartCode    脚本代码
     * @param searchData   查询参数
     * @param currentUser  操作人
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 统计数据
     */
    @SuppressWarnings("rawtypes")
    public RetResult<ProcessMapItem> getChartTotalData(String chartCode, RMap searchData, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        return handleScript(chartCode, ERPModule.CHARTS, dataSourceId, supplierCode,
                () -> ProcessMapItem.newBuilder()
                        .itemData(searchData)
                        .currentUser(currentUser)
                        .dataSourceId(dataSourceId)
                        .supplierCode(supplierCode)
                        .build()
        );
    }
    //endregion

    //region saas租户管理
    //当前仅限商城saas服务使用

    public void initSaasClient(DataBaseMultiItemEx dataItem, String dataSourceId, long supplierCode) {
        CompletableFuture.runAsync(() -> {
            RetResult<ProcessStringItem> execResult = handleScript("System_Init_ERPClient", ERPModule.SYSTEM, dataSourceId, supplierCode,
                    () -> new ProcessERPClientItem(dataItem)
            );
            if (!execResult.isSuccess()) {
                logger.error("执行初始化错误" + execResult.getRetinfo());
            }
        }, initExecutor);
    }

    public void voidSaasClient(DataBaseMultiItemEx dataItem, String dataSourceId, long supplierCode) {
        CompletableFuture.runAsync(() -> {
            RetResult<ProcessStringItem> execResult = handleScript("System_Void_ERPClient", ERPModule.SYSTEM, dataSourceId, supplierCode,
                    () -> new ProcessERPClientItem(dataItem)
            );
            if (!execResult.isSuccess()) {
                logger.error("执行初始化错误" + execResult.getRetinfo());
            }
        }, initExecutor);
    }

    //endregion

    public RetResult<InvokeCallResult> callSystem(InvokeCallParams source, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (source == null) {
            return RetResult.<InvokeCallResult>errorT().retinfo("无效的参数信息");
        }

        if (StringUtils.isEmpty(source.getBusinessMethod())) {
            return RetResult.<InvokeCallResult>errorT().retinfo("无效的参数方法[businessMethod]信息");
        }

        source.setCurrentUser(currentUser);
        source.setDataSourceId(dataSourceId);
        source.setSupplierCode(supplierCode);

        return callScript(source.getBusinessMethod(), source, currentUser, dataSourceId, supplierCode);
    }
}
