package cn.axzo.msg.center.message.service.impl;

import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.common.redis.RedisUtil;
import cn.axzo.msg.center.dal.MessageGroupNodeDao;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodeHolder;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodePathDTO;
import cn.axzo.msg.center.message.domain.dto.MessageGroupTreeNodeDto;
import cn.axzo.msg.center.message.service.MessageGroupTreeNodeCacheService;
import cn.axzo.msg.center.service.dto.GroupTreeNodeDTO;
import cn.axzo.msg.center.utils.TreeHelperUtil;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;

/**
 * @author cold_blade
 * @date 2023/10/14
 * @version 1.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageGroupTreeNodeCacheServiceImpl implements MessageGroupTreeNodeCacheService {

    private static final String CACHE_KEY = "msg-center:message:group:cache-key";
    private static final String CACHE_VALUE = "msg-center:message:group:cache-val";
    private static final long CACHE_KEY_TIMEOUT_DAYS = 1;

    private final RedisUtil redisUtil;
    private final MessageGroupNodeDao messageGroupNodeDao;

    @Override
    public List<GroupTreeNodeDTO> listAllGroupTree() {
        return getAllGroupTreesNoCache();
    }

    @Override
    public Optional<GroupTreeNodeDTO> queryRootNode(String rootNodeCode) {
        if (StringUtils.isBlank(rootNodeCode)) {
            log.info("rootNodeCode is blank.");
            return Optional.empty();
        }
        return getAllGroupTreesNoCache().stream()
                .filter(e -> Objects.equals(e.getNodeCode(), rootNodeCode))
                .findFirst();
    }

    @Override
    public Optional<GroupTreeNodeDTO> queryNode(String groupNodeCode) {
        if (StringUtils.isBlank(groupNodeCode)) {
            log.info("groupNodeCode is blank.");
            return Optional.empty();
        }
        return getAllGroupTreesNoCache().stream()
                .map(e -> findTreeNode(e, groupNodeCode))
                .filter(Objects::nonNull)
                .findFirst();
    }

    @Override
    public Optional<GroupTreeNodePathDTO> queryLeafNodePath(String leafNodeCode) {
        if (StringUtils.isBlank(leafNodeCode)) {
            log.info("leafNodeCode is blank.");
            return Optional.empty();
        }
        return getLeafTreeNodePathsNoCache().stream()
                .filter(e -> Objects.equals(e.getNodeCode(), leafNodeCode))
                .findFirst();
    }

    @Override
    public List<GroupTreeNodePathDTO> listLeafNodePaths(String rootNodeCode) {
        if (StringUtils.isBlank(rootNodeCode)) {
            log.info("leafNodeCode is blank.");
            return Collections.emptyList();
        }
        return getLeafTreeNodePathsNoCache().stream()
                .filter(e -> e.getNodeCodePath().startsWith(rootNodeCode))
                .collect(toList());
    }

    @Override
    public synchronized void refreshCache() {
        // 清除redis中的缓存标识
        redisUtil.getKeyOps().delete(CACHE_KEY);
        // 本地缓存初始化并更新redis中的缓存标识
        initialize(true);
    }

    private GroupTreeNodeDTO findTreeNode(GroupTreeNodeDTO root, String treeNodeCode) {
        if (Objects.equals(root.getNodeCode(), treeNodeCode)) {
            return root;
        }
        LinkedList<GroupTreeNodeDTO> queue = new LinkedList<>(root.getNodeChildren());
        while (!queue.isEmpty()) {
            GroupTreeNodeDTO node = queue.pop();
            if (Objects.equals(node.getNodeCode(), treeNodeCode)) {
                return node;
            }
            queue.addAll(node.getNodeChildren());
        }
        return null;
    }

    private List<GroupTreeNodeDTO> getAllGroupTreesCache() {
        MessageGroupTreeNodeDto node = GroupTreeNodeHolder.getNode();
        if (redisUtil.getKeyOps().hasKey(CACHE_KEY)) {
            // 其它结点进行了本地缓存，但是当前服务进程还未进行缓存
            if (Objects.isNull(node) || CollectionUtils.isEmpty(node.getAllGroupTreesCache())) {
                // 本地缓存初始化，不更新redis中的缓存标识
                initialize(false);
            }
        } else {
            // 本地缓存初始化并更新redis中的缓存标识
            initialize(true);
        }
        node = GroupTreeNodeHolder.getNode();
        return node.getAllGroupTreesCache();
    }

    private List<GroupTreeNodePathDTO> getLeafTreeNodePathsCache() {
        MessageGroupTreeNodeDto node = GroupTreeNodeHolder.getNode();
        if (redisUtil.getKeyOps().hasKey(CACHE_KEY)) {
            // 其它结点进行了本地缓存，但是当前服务进程还未进行缓存
            if (Objects.isNull(node) || CollUtil.isEmpty(node.getLeafTreeNodePathsCache())) {
                // 本地缓存初始化，不更新redis中的缓存标识
                initialize(false);
            }
        } else {
            // 本地缓存初始化并更新redis中的缓存标识
            initialize(true);
        }
        node = GroupTreeNodeHolder.getNode();
        return node.getLeafTreeNodePathsCache();
    }

    private List<GroupTreeNodePathDTO> getLeafTreeNodePathsNoCache() {
        List<MessageGroupNode> groupNodes = listAllValidNodesFromDB();
        List<GroupTreeNodeDTO> allGroupTreesCache = TreeHelperUtil.buildTrees(groupNodes);
        List<GroupTreeNodePathDTO> leafTreeNodePaths = allGroupTreesCache.stream()
                .flatMap(e -> parseRootNode(e).stream())
                .collect(toList());
        return leafTreeNodePaths;
    }

    private List<GroupTreeNodeDTO> getAllGroupTreesNoCache() {
        List<MessageGroupNode> groupNodes = listAllValidNodesFromDB();
        List<GroupTreeNodeDTO> allGroupTrees = TreeHelperUtil.buildTrees(groupNodes);
        return allGroupTrees;
    }

    private synchronized void initialize(boolean refreshCache) {
        List<MessageGroupNode> groupNodes = listAllValidNodesFromDB();
        List<GroupTreeNodeDTO> allGroupTreesCache = TreeHelperUtil.buildTrees(groupNodes);
        List<GroupTreeNodePathDTO> leafTreeNodePathsCache = allGroupTreesCache.stream()
                .flatMap(e -> parseRootNode(e).stream())
                .collect(toList());

        log.info("currentThread= [{}] cache node, refreshRedisCache flag= [{}]",  Thread.currentThread().getName(),
                refreshCache);
        GroupTreeNodeHolder.setNode(MessageGroupTreeNodeDto.builder()
                .allGroupTreesCache(allGroupTreesCache)
                .leafTreeNodePathsCache(leafTreeNodePathsCache)
                .build());
        if (refreshCache) {
            redisUtil.getStringOps().setIfAbsent(CACHE_KEY, CACHE_VALUE, CACHE_KEY_TIMEOUT_DAYS, TimeUnit.DAYS);
        }
    }

    private List<MessageGroupNode> listAllValidNodesFromDB() {
        return messageGroupNodeDao.lambdaQuery()
                .eq(MessageGroupNode::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .list();
    }

    private List<GroupTreeNodePathDTO> parseRootNode(GroupTreeNodeDTO root) {
        List<GroupTreeNodePathDTO> paths = new ArrayList<>();
        List<GroupTreeNodeDTO> stack = new ArrayList<>();
        parse(root, stack, paths);
        return paths;
    }

    private void parse(GroupTreeNodeDTO node, List<GroupTreeNodeDTO> stack, List<GroupTreeNodePathDTO> paths) {
        if (!stack.isEmpty()
                && !Objects.equals(stack.get(stack.size() - 1).getNodeCode(), node.getParentNodeCode())) {
            stack.remove(stack.size() - 1);
        }
        stack.add(node);
        if (CollectionUtils.isEmpty(node.getNodeChildren())) {
            // 当前结点为树的叶结点（逻辑上的，有可能不是业务维度的叶结点）
            paths.add(GroupTreeNodePathDTO.of(node, stack));
            stack.remove(node);
        } else {
            node.getNodeChildren().forEach(e -> parse(e, stack, paths));
        }
    }

    private List<GroupTreeNodeDTO> reverseStack(LinkedList<GroupTreeNodeDTO> pathNodeStack) {
        List<GroupTreeNodeDTO> list = new ArrayList<>();
        for (GroupTreeNodeDTO node : pathNodeStack) {
            list.add(0, node);
        }
        return list;
    }

}
