package cn.cslg.report.service.business;


import cn.cslg.report.common.utils.Response;
import cn.cslg.report.entity.ReportField;
import cn.cslg.report.entity.ReportFieldOption;
import cn.cslg.report.entity.ReportFieldTree;
import cn.cslg.report.mapper.ReportFieldTreeMapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * <p>
 * 自定义字段树类型关联表 服务实现类
 * </p>
 *
 * @author 王岩
 * @since 2021-12-17
 */
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class ReportFieldTreeService extends ServiceImpl<ReportFieldTreeMapper, ReportFieldTree> {

    private final ReportFieldPatentLinkService reportFieldPatentLinkService;
    private final ReportFieldService reportFieldService;

    public  List<ReportFieldTree> getProjectFieldTreeOptionByIds(List<Integer> ids) {
        if (ids == null || ids.size() == 0) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ReportFieldTree::getId, ids);
        queryWrapper.orderByAsc(ReportFieldTree::getOrder);
        return this.list(queryWrapper);

    }

    public Integer getTreeNodeIdByPathNameAndUpdateTreeData(List<String> pathName, ReportField projectField) {
        List<Integer> treeNode = new ArrayList<>();
        for (int i = 0; i < pathName.size(); i++) {
            ReportFieldTree projectFieldTree = this.getReportFieldTreeOptionByFieldIdAndNameAndLevel(projectField.getId(), (i == 0 ? 0 : treeNode.get(i - 1)), pathName.get(i), i + 1);
            if (projectFieldTree == null) {
                projectFieldTree = new ReportFieldTree();
                projectFieldTree.setFieldId(projectField.getId());
                projectFieldTree.setName(pathName.get(i));
                projectFieldTree.setLevel(i + 1);
                projectFieldTree.insert();
            }
            treeNode.add(projectFieldTree.getId());
            projectFieldTree.setTreeId(projectFieldTree.getId());
            projectFieldTree.setOrder(projectFieldTree.getId());
            projectFieldTree.setPath(StringUtils.join(treeNode, "/"));
            if (treeNode.size() == 1) {
                projectFieldTree.setParentId(0);
            } else {
                projectFieldTree.setParentId(treeNode.get(i - 1));
            }
            projectFieldTree.updateById();
        }
        if (treeNode.size() == 0) {
            return 0;
        }
        return treeNode.get(treeNode.size() - 1);
    }

    public List<ReportFieldTree> getReportFieldTreeOptionByFieldId(Integer fieldId) {
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ReportFieldTree::getFieldId, fieldId);
        queryWrapper.orderByAsc(ReportFieldTree::getOrder);
        return this.list(queryWrapper);
    }

    public ReportFieldTree getReportFieldTreeNodeByFieldIdAndParentIdAndName(Integer fieldId, Integer parentId, String name) {
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ReportFieldTree::getFieldId, fieldId);
        queryWrapper.eq(ReportFieldTree::getParentId, parentId);
        queryWrapper.eq(ReportFieldTree::getName, name);
        return this.getOne(queryWrapper);
    }

    public ReportFieldTree getReportFieldTreeOptionByFieldIdAndNameAndLevel(Integer fieldId, Integer parentId, String name, Integer level) {
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ReportFieldTree::getFieldId, fieldId);
        queryWrapper.eq(ReportFieldTree::getName, name);
        queryWrapper.eq(ReportFieldTree::getLevel, level);
        queryWrapper.eq(ReportFieldTree::getParentId, parentId);
        return this.getOne(queryWrapper);
    }

    public ReportFieldTree getReportFieldTreeOptionByFieldIdAndNameAndLevel(Integer fieldId, String name, Integer level) {
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ReportFieldTree::getFieldId, fieldId);
        queryWrapper.eq(ReportFieldTree::getName, name);
        queryWrapper.eq(ReportFieldTree::getLevel, level);
        return this.getOne(queryWrapper);
    }

    public List<ReportFieldTree> getReportFieldTreeOptionByFieldIds(List<Integer> fieldIds) {
        if (fieldIds == null || fieldIds.size() == 0) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ReportFieldTree::getFieldId, fieldIds);
        queryWrapper.orderByAsc(ReportFieldTree::getOrder);
        return this.list(queryWrapper);
    }

    public List<ReportFieldTree> getReportFieldTreeOptionByIds(List<Integer> ids) {
        if (ids == null || ids.size() == 0) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ReportFieldTree::getId, ids);
        queryWrapper.orderByAsc(ReportFieldTree::getOrder);
        return this.list(queryWrapper);
    }
    public List<ReportFieldTree> getReportFieldTree(Integer id,Integer reportID){
        LambdaQueryWrapper<ReportFieldTree> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ReportFieldTree::getFieldId,id)
                .in(ReportFieldTree::getReportId, reportID);
        queryWrapper.orderByAsc(ReportFieldTree::getOrder);
        return this.list(queryWrapper);
    }
    public List<Tree<Integer>> get(Integer id,Integer reportID){
        List<ReportFieldTree> dataList = this.getReportFieldTree(id, reportID);

        for (ReportFieldTree tree : dataList) {
            List<ReportFieldTree> tempList = dataList.stream().filter(item -> item.getParentId().equals(tree.getTreeId())).collect(Collectors.toList());
            tempList.forEach(item -> item.setParentId(tree.getId()));
        }
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        treeNodeConfig.setWeightKey("order");
        List<TreeNode<Integer>> nodeList = CollUtil.newArrayList();
        for (ReportFieldTree data : dataList) {
            nodeList.add(new TreeNode<>(data.getId(), data.getParentId(), data.getName(), data.getOrder()));
        }
        List<Tree<Integer>> treeNodes = TreeUtil.build(nodeList, 0, treeNodeConfig, (treeNode, tree) -> {
            ReportFieldTree temp = dataList.stream().filter(item -> item.getId().equals(treeNode.getId())).findFirst().orElse(new ReportFieldTree());
            tree.setId(treeNode.getId());
            tree.setParentId(treeNode.getParentId());
            tree.setWeight(treeNode.getWeight());
            tree.setName(treeNode.getName());
            tree.putExtra("level", temp.getLevel());
            tree.putExtra("path", temp.getPath());
            tree.putExtra("fieldId", temp.getFieldId());
        });
        return treeNodes;

    }



    public List<Tree<Integer>> getReportFieldTreeNodeByFieldId(Integer fieldId) {
        List<ReportFieldTree> dataList = this.getReportFieldTreeOptionByFieldId(fieldId);
        for (ReportFieldTree tree : dataList) {
            List<ReportFieldTree> tempList = dataList.stream().filter(item -> item.getParentId().equals(tree.getTreeId())).collect(Collectors.toList());
            tempList.forEach(item -> item.setParentId(tree.getId()));
        }
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        treeNodeConfig.setWeightKey("order");
        List<TreeNode<Integer>> nodeList = CollUtil.newArrayList();
        for (ReportFieldTree data : dataList) {
            nodeList.add(new TreeNode<>(data.getId(), data.getParentId(), data.getName(), data.getOrder()));
        }
        List<Tree<Integer>> treeNodes = TreeUtil.build(nodeList, 0, treeNodeConfig, (treeNode, tree) -> {
            ReportFieldTree temp = dataList.stream().filter(item -> item.getId().equals(treeNode.getId())).findFirst().orElse(new ReportFieldTree());
            tree.setId(treeNode.getId());
            tree.setParentId(treeNode.getParentId());
            tree.setWeight(treeNode.getWeight());
            tree.setName(treeNode.getName());
            tree.putExtra("level", temp.getLevel());
            tree.putExtra("path", temp.getPath());
            tree.putExtra("fieldId", temp.getFieldId());
        });
        return treeNodes;
    }

    public Integer getFieldTreeNodeTotal(Integer fieldId) {
        return (int) this.count(Wrappers.<ReportFieldTree>lambdaQuery().eq(ReportFieldTree::getFieldId, fieldId));
    }

    public Integer getFieldTreeParentId(Integer id) {
        ReportFieldTree tree = this.getById(id);
        if (tree != null) {
            return tree.getTreeId();
        }
        return 0;
    }

    public Integer[] getFieldTreeNodePath(Integer id, Integer parentId) {
        ReportFieldTree parentNode = this.getById(parentId);
        String[] nodePath = parentNode.getPath().split("/");
        Integer[] nodeIds = new Integer[nodePath.length + 1];
        for (int i = 0; i < nodeIds.length - 1; i++) {
            nodeIds[i] = Integer.parseInt(nodePath[i]);
        }
        nodeIds[nodePath.length] = id;
        return nodeIds;
    }

    @Transactional
    public Integer add(ReportFieldTree tree) {
    Integer total = this.getFieldTreeNodeTotal(tree.getFieldId());
        tree.setOrder(-1);
        tree.insert();
        this.edit(tree);
        return tree.getId();
    }

    @Transactional
    public void edit(ReportFieldTree tree) {
        Integer[] path = tree.getParentId() == 0 ? new Integer[]{tree.getId()} : this.getFieldTreeNodePath(tree.getId(), tree.getParentId());
        tree.setPath(StringUtils.join(path, "/"));
        tree.setLevel(path.length);
        tree.setParentId(this.getFieldTreeParentId(tree.getParentId()));
        tree.setTreeId(tree.getId());
        if (tree.getOrder() == -1) {
            tree.setOrder(tree.getId());
        }
        tree.updateById();
    }

    @Transactional
    public void addBatch(List<ReportFieldOption> optionList, Integer fieldId) {
        this.saveOrUpdateOption(optionList, fieldId, 0);
        this.updateNodePath(fieldId);
    }

    @Transactional
    public void editBatch(List<ReportFieldOption> optionList, Integer fieldId) {
        List<ReportFieldTree> tempList = this.getReportFieldTreeOptionByFieldId(fieldId);
        List<Integer> nodeIds = this.getTreeNodeIds(optionList, new ArrayList<>());
        List<Integer> deleteIds = tempList.stream().map(ReportFieldTree::getId).filter(id -> !nodeIds.contains(id)).collect(Collectors.toList());
        this.deleteByIds(deleteIds);
        this.saveOrUpdateOption(optionList, fieldId, 0);
        this.updateNodePath(fieldId);
    }

    private List<Integer> getTreeNodeIds(List<ReportFieldOption> optionList, List<Integer> ids) {
        for (ReportFieldOption option : optionList) {
            if (option.getId() != null) {
                ids.add(option.getId());
            }
            if (option.getChildren() != null && option.getChildren().size() != 0) {
                this.getTreeNodeIds(option.getChildren(), ids);
            }
        }
        return ids;
    }

    private void saveOrUpdateOption(List<ReportFieldOption> optionList, Integer fieldId, Integer parentId) {
        for (ReportFieldOption option : optionList) {
            option.setParentId(parentId);
            option.setFieldId(fieldId);
            Integer id = this.saveOption(option);
            if (option.getChildren() != null && option.getChildren().size() != 0) {
                this.saveOrUpdateOption(option.getChildren(), fieldId, id);
            }
        }
    }

    private Integer saveOption(ReportFieldOption option) {
        ReportFieldTree tree = new ReportFieldTree();
        tree.setId(option.getId());
        tree.setName(option.getName());
        tree.setFieldId(option.getFieldId());
        tree.setOrder(option.getOrder());
        tree.setParentId(option.getParentId());
        if (tree.getId() == null) {
            tree.insert();
        }
        tree.setTreeId(tree.getId());
        tree.updateById();
        return tree.getId();
    }

    private void updateNodePath(Integer fieldId) {
        List<ReportFieldTree> treeList = this.getReportFieldTreeOptionByFieldId(fieldId);
        treeList.forEach(tree -> {
            Integer[] paths = this.findTreeNodePath(tree.getId());
            tree.setLevel(paths.length);
            tree.setPath(StringUtils.join(paths, "/"));
        });
        this.saveOrUpdateBatch(treeList);
    }

    public Integer[] findTreeNodePath(Integer id) {
        List<Integer> paths = new ArrayList<>();
        List<Integer> parentPath = this.findParentPath(id, paths);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Integer[parentPath.size()]);
    }

    private List<Integer> findParentPath(Integer id, List<Integer> paths) {
        paths.add(id);
        ReportFieldTree tree = this.getById(id);
        if (tree.getParentId() != 0) {
            findParentPath(tree.getParentId(), paths);
        }
        return paths;
    }

    public void deleteByFieldId(Integer fieldId) {
        this.remove(Wrappers.<ReportFieldTree>lambdaQuery().eq(ReportFieldTree::getFieldId, fieldId));
    }

    private List<Integer> findTreeNodes(Integer id, List<Integer> nodes) {
        nodes.add(id);
        List<ReportFieldTree> treeList = this.list(Wrappers.<ReportFieldTree>lambdaQuery().eq(ReportFieldTree::getParentId, id));
        if (treeList != null && treeList.size() != 0) {
            treeList.forEach(item -> {
                nodes.add(item.getId());
                this.findTreeNodes(item.getId(), nodes);
            });
        }
        return nodes;
    }

    @Transactional
    public void delete(Integer id) {
        List<Integer> nodes = this.findTreeNodes(id, new ArrayList<>());
        reportFieldPatentLinkService.deleteByOptionIds(nodes);
        this.removeByIds(nodes);
    }

    @Transactional
    public void deleteByIds(List<Integer> ids) {
        if (ids != null && ids.size() != 0) {
            reportFieldPatentLinkService.deleteByOptionIds(ids);
            this.removeByIds(ids);
        }
    }

    @Transactional
    public String updateByReportFieldTreeNode(ReportFieldOption projectFieldOption) {
        ReportFieldTree temp = this.getReportFieldTreeNodeByFieldIdAndParentIdAndName(projectFieldOption.getFieldId(), projectFieldOption.getParentId(), projectFieldOption.getName());
        if (temp != null && !temp.getId().equals(projectFieldOption.getId())) {
            return Response.error("选项名称已存在");
        }
        ReportFieldTree projectFieldTree = new ReportFieldTree();
        projectFieldTree.setId(projectFieldOption.getId());
        projectFieldTree.setFieldId(projectFieldOption.getFieldId());
        projectFieldTree.setOrder(projectFieldOption.getOrder());
        projectFieldTree.setParentId(projectFieldOption.getParentId());
        projectFieldTree.setName(projectFieldOption.getName());
        projectFieldTree.setReportId(projectFieldOption.getReportId());
//        projectFieldTree.setReportId();
        projectFieldTree.insertOrUpdate();
        this.edit(projectFieldTree);
        return Response.success(projectFieldTree.getId());
    }
}
