package cn.fateverse.query.service;

import cn.fateverse.query.entity.UniColumn;
import cn.fateverse.query.entity.UniCon;
import cn.fateverse.query.entity.bo.TopologySelect;
import cn.fateverse.query.entity.topology.*;
import cn.hutool.core.util.StrUtil;
import cn.fateverse.common.core.exception.CustomException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Clay
 * @date 2023-07-22
 */
@Component
public class TopologyAnalyzeService {

    /**
     * 初始化表格的json信息,转换为对象的node和edge
     *
     * @param json 拓扑JSON信息
     * @return 拓扑解析完成后的对象信息
     */
    public TopologyBo initTopologyJson(String json) {
        try {
            JSONObject topoObject = JSON.parseObject(json);
            List<Node> nodes = topoObject.getList("nodes", Node.class);
            if (nodes == null || nodes.isEmpty()) {
                throw new JSONException("拓扑图结构有误");
            }
            List<Edge> edges = topoObject.getList("edges", Edge.class);
            return new TopologyBo(nodes, edges);
        } catch (JSONException e) {
            throw new CustomException("拓扑图数据解析失败!");
        }
    }

    /**
     * 单表查询分析
     *
     * @param node    节点信息
     * @param queryId 自定义查询id
     * @return 拓扑图查询对象
     */
    public TopologySelect singleTableAnalyze(Node node, Long queryId) {
        //查询的key信息
        StringBuilder selectKeyBuilder = new StringBuilder();
        //获取到节点的列信息
        List<NodeColumn> columns = node.getColumns();
        //条件信息
        List<UniCon> uniConList = new ArrayList<>();
        //展示的列信息
        List<UniColumn> uniColumns = new ArrayList<>();
        //遍历列信息
        columns.forEach(column -> {
            //判断当前列是否使用
            if (column.getUse()) {
                selectKeyBuilder.append(column.getColumnName()).append(",");
                UniColumn uniColumn = UniColumn.builder()
                        .queryId(queryId)
                        .sort(column.getSort())
                        .prop(column.getColumnName())
                        .label(column.getColumnComment())
                        .displayType(column.getDisplayType())
                        .displayParam(column.getDisplayParam())
                        .build();
                uniColumns.add(uniColumn);
            }
            //判断当前列是否查询
            if (column.getQuery()) {
                UniCon uniCon = UniCon.builder()
                        .uqId(queryId)
                        .ucName(column.getColumnComment())
                        .ucKey(column.getColumnName())
                        .type(column.getType())
                        .ucCon(column.getUcCon())
                        .ucType(column.getUcType())
                        .sort(column.getSort())
                        .ucMock(column.getUcMock())
                        .ucDescribe(column.getUcDescribe())
                        .build();
                uniConList.add(uniCon);
            }
        });
        String selectKey = selectKeyBuilder.toString();
        selectKey = selectKey.substring(0, selectKeyBuilder.length() - 1);
        return TopologySelect.builder()
                .selectKey(selectKey)
                .tableInfo(node.getTable())
                .uniColumns(uniColumns)
                .uniCons(uniConList)
                .build();
    }

    /**
     * 多表分析
     *
     * @param topology 拓扑图对象
     * @param queryId  自定义查询id
     * @return 拓扑图查询对象
     */
    public TopologySelect multiListAnalyze(TopologyBo topology, Long queryId) {
        List<Edge> edges = topology.getEdges();
        if (null == edges || edges.isEmpty() || edges.size() < topology.getNodes().size() - 1) {
            throw new CustomException("线条数量有误,存在图未连接");
        }
        //根据源id归并连线信息
        Map<String, List<Edge>> edgeSourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource));
        //根据目标id归并连线信息
        Map<String, List<Edge>> edgeTargetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget));
        //根据节点id整理映射关系
        Map<String, Node> nodeMap = topology.getNodes().stream().collect(Collectors.toMap(Node::getId, Function.identity()));
        //获取到主节点
        Node node = queryRootNode(nodeMap, edgeTargetMap, edges.get(0).getSource());
        //创建拓扑查询对象
        TopologySelect topologySelect = new TopologySelect();
        //创建节点分析对象
        NodeAnalyze nodeAnalyze = new NodeAnalyze();
        //添加主节点的信息
        nodeAnalyze.addNodeAnalyzeInfo(node, queryId);
        //批量分析其他节点信息
        multiListAnalyzeNode(nodeMap, edgeSourceMap, nodeAnalyze, node, queryId);
        //获取到查询key
        String selectKey = nodeAnalyze.getSelectKey().toString();
        //删除最后节点的逗号
        topologySelect.setSelectKey(selectKey.substring(0, selectKey.length() - 1));
        //设置关系
        topologySelect.setRelevance(nodeAnalyze.getRelevance());
        //设置查询展示的列信息
        topologySelect.setUniColumns(nodeAnalyze.getUniColumns());
        //设置查询条件
        topologySelect.setUniCons(nodeAnalyze.getUniCons());
        //设置表格信息
        topologySelect.setTableInfo(node.getTable() + " " + node.getAlias());
        //返回对象
        return topologySelect;
    }

    /**
     * 多表节点解析
     *
     * @param nodeMap       节点映射映射信息
     * @param edgeSourceMap 当前表格的连线信息
     * @param nodeAnalyze   节点分析对象
     * @param root          上级节点
     * @param queryId       自定义查询id
     */
    private void multiListAnalyzeNode(Map<String, Node> nodeMap, Map<String, List<Edge>> edgeSourceMap, NodeAnalyze nodeAnalyze, Node root, Long queryId) {
        List<Edge> edgeList = edgeSourceMap.get(root.getId());
        if (edgeList == null || edgeList.isEmpty()) {
            return;
        }
        for (Edge edge : edgeList) {
            Node node = nodeMap.get(edge.getTarget());
            //添加节点的信息
            nodeAnalyze.addNodeAnalyzeInfo(node, queryId);
            //添加关系
            nodeAnalyze.addRelevance(node, root, edge);
            //递归分析
            multiListAnalyzeNode(nodeMap, edgeSourceMap, nodeAnalyze, node, queryId);
        }
    }

    /**
     * 插叙到主节点
     *
     * @param nodeMap       节点映射映射信息
     * @param edgeTargetMap 当前表格的连线信息
     * @param nodeId        节点id
     * @return 主节点
     */
    private Node queryRootNode(Map<String, Node> nodeMap, Map<String, List<Edge>> edgeTargetMap, String nodeId) {
        List<Edge> edges = edgeTargetMap.get(nodeId);
        if (null == edges || edges.isEmpty()) {
            return nodeMap.get(nodeId);
        }
        Optional<Edge> optional = edges.stream().filter(edge -> !edge.getSource().equals(edge.getTarget())).findFirst();
        if (optional.isEmpty()) {
            return nodeMap.get(nodeId);
        }
        Edge edge = optional.get();
        return queryRootNode(nodeMap, edgeTargetMap, edge.getSource());
    }


}


@Data
class NodeAnalyze {
    /**
     * 表格别名
     */
    private Set<String> tableAlias = new HashSet<>();
    /**
     * 列表名称
     */
    private Set<String> tableColumnSet = new HashSet<>();
    /**
     * 查询的key
     */
    private StringBuilder selectKey = new StringBuilder("");
    /**
     * 关联关系
     */
    private List<String> relevance = new ArrayList<>();
    /**
     * 使用的列表信息
     */
    private List<UniColumn> uniColumns = new ArrayList<>();
    /**
     * 查询条件
     */
    private List<UniCon> uniCons = new ArrayList<>();

    /**
     * 设置设置表的别名
     *
     * @param node 节点信息
     */
    public void setTableAlias(Node node) {
        String[] strList = node.getTable().toLowerCase().split("_");
        StringBuilder sqlStr = new StringBuilder();
        for (String str : strList) {
            sqlStr.append(str.charAt(0));
        }
        Random random = new Random();
        //如果表格被使用,则在后方随机添加英文字母
        while (tableAlias.contains(sqlStr.toString())) {
            char c = (char) (random.nextInt(26) + 97);
            sqlStr.append(c);
        }
        tableAlias.add(sqlStr.toString());
        node.setAlias(sqlStr.toString());
    }

    /**
     * 添加关联关系
     *
     * @param node 节点数据
     * @param root 上级节点
     * @param edge 连线信息
     */
    public void addRelevance(Node node, Node root, Edge edge) {
        EdgeConfig appConfig = edge.getAppConfig();
        checkEdgeConfig(appConfig);
        String relevance = appConfig.getAssociated() + " join " + node.getTable() + " " + node.getAlias() + " on "
                + node.getAlias() + "." + appConfig.getTargetColumn()
                + " = " + root.getAlias() + "." + appConfig.getSourceColumn() + "\r";
        this.relevance.add(relevance);
    }

    /**
     * 检查连接配置
     *
     * @param appConfig 连线配置
     */
    private void checkEdgeConfig(EdgeConfig appConfig) {
        if (StrUtil.isBlank(appConfig.getAssociated())) {
            throw new CustomException("存在连接没有选择关系");
        }
        if (StrUtil.isBlank(appConfig.getSourceColumn())) {
            throw new CustomException("存在连接没有主表key");
        }
        if (StrUtil.isBlank(appConfig.getTargetColumn())) {
            throw new CustomException("存在连接没有子表key");
        }
    }

    /**
     * 添加node,并分析基础信息
     *
     * @param node    节点信息
     * @param queryId 查询id
     */
    public void addNodeAnalyzeInfo(Node node, Long queryId) {
        setTableAlias(node);
        List<NodeColumn> columns = node.getColumns();
        if (null == columns || columns.isEmpty()) {
            return;
        }
        for (NodeColumn column : columns) {
            String key = node.getAlias() + "." + column.getColumnName();
            //检查当前列是否需要展示
            if (column.getUse() && !tableColumnSet.contains(column.getColumnName())) {
                selectKey.append(key).append(",");
                UniColumn uniColumn = UniColumn.builder()
                        .queryId(queryId)
                        .sort(column.getSort())
                        .prop(column.getColumnName())
                        .label(column.getColumnComment())
                        .displayType(column.getDisplayType())
                        .displayParam(column.getDisplayParam())
                        .build();
                tableColumnSet.add(column.getColumnName());
                uniColumns.add(uniColumn);
            }
            //如果当前列需要查询,则整理查询条件
            if (column.getQuery()) {
                UniCon uniCon = UniCon.builder()
                        .uqId(queryId)
                        .ucName(column.getColumnComment())
                        .ucKey(key)
                        .type(column.getType())
                        .ucCon(column.getUcCon())
                        .ucType(column.getUcType())
                        .ucMock(column.getUcMock())
                        .ucDescribe(column.getUcDescribe())
                        .build();
                uniCons.add(uniCon);
            }
        }

    }
}