package com.supervision.neo4j.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.supervision.common.domain.R;
import com.supervision.common.utils.StringUtils;
import com.supervision.neo4j.domain.CaseNode;
import com.supervision.neo4j.domain.Rel;
import com.supervision.neo4j.dto.WebRelDTO;
import com.supervision.neo4j.service.Neo4jService;
import com.supervision.neo4j.utils.Neo4jUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.cypherdsl.core.Case;
import org.neo4j.driver.*;
import org.neo4j.driver.Record;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author qmy
 * @since 2023-10-26
 */
@Slf4j
@Service
public class Neo4jServiceImpl implements Neo4jService {

    private final Driver driver;

    @Autowired
    private Neo4jServiceImpl(Driver driver) {
        this.driver = driver;
    }

    @Override
    public CaseNode save(CaseNode caseNode) {
        if (StringUtils.isEmpty(caseNode.getName()) || StringUtils.isEmpty(caseNode.getNodeType())) {
            throw new RuntimeException("未传节点名称或节点类型或图谱类型!");
        }
        List<CaseNode> byName = findByName(caseNode.getCaseId(), caseNode.getRecordId(), caseNode.getNodeType(), caseNode.getName(), caseNode.getPicType());
        if (byName != null && !byName.isEmpty()) {
            throw new RuntimeException("名称已存在!");
        }
        CaseNode res = null;
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            Map<String, Object> params = new HashMap<>();
            cql.append("CREATE (n:").append(caseNode.getNodeType()).append("{name:$name");
            params.put("name", caseNode.getName());
            if (StringUtils.isNotEmpty(caseNode.getRecordId())) {
                cql.append(", recordSplitId:$recordSplitId");
                params.put("recordSplitId", caseNode.getRecordSplitId());
            }
            if (StringUtils.isNotEmpty(caseNode.getRecordId())) {
                cql.append(", recordId:$recordId");
                params.put("recordId", caseNode.getRecordId());
            }
            if (StringUtils.isNotEmpty(caseNode.getCaseId())) {
                cql.append(", caseId:$caseId");
                params.put("caseId", caseNode.getCaseId());
            }
            if (StringUtils.isNotEmpty(caseNode.getPicType())) {
                cql.append(", picType:$picType");
                params.put("picType", caseNode.getPicType());
            }
            cql.append("})").append(Neo4jUtils.NODE_RETURN);
            Result run = session.run(cql.toString(), params);
            res = Neo4jUtils.getOneNode(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 删除节点,注意,删除节点的时候,要先把边删除掉,不然会报错
     *
     * @param id 点的ID
     */
    @Override
    public void delNode(Long id) {
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            cql.append("MATCH (n) where id(n) = ").append(id).append(" DELETE n");
            log.info(cql.toString());
            Result run = session.run(cql.toString());
            while (run.hasNext()) {
                Record next = run.next();
                // log.info(next.toString());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 删除边
     *
     * @param relId 边ID
     */
    public void deleteRel(Long relId) {
        try {
            Session session = driver.session();
            StringBuilder cql = new StringBuilder();
            cql.append("MATCH ()-[r]->() WHERE id(r) = ").append(relId).append(" DELETE r");
            log.info(cql.toString());
            Result run = session.run(cql.toString());
            while (run.hasNext()) {
                Record next = run.next();
                // log.info(next.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public CaseNode findById(Long id) {
        CaseNode node = null;
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            cql.append("MATCH (n) where id(n) = ").append(id).append(Neo4jUtils.NODE_RETURN);
            Result run = session.run(cql.toString());
            node = Neo4jUtils.getOneNode(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return node;
    }

    @Override
    public List<CaseNode> findByName(String caseId, String recordId, String nodeType, String name, String picType) {
        List<CaseNode> list = new ArrayList<>();
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            cql.append("MATCH (n");
            if (StringUtils.isNotEmpty(nodeType)) {
                cql.append(":");
                cql.append(nodeType);
            }
            cql.append(") where 1 = 1");
            if (StringUtils.isNotEmpty(caseId)) {
                cql.append(" and n.caseId = ");
                cql.append(caseId);
            }
            if (StringUtils.isNotEmpty(recordId)) {
                cql.append(" and n.recordId = ");
                cql.append(recordId);
            }
            if (StringUtils.isNotEmpty(name)) {
                cql.append(" and n.name = '");
                cql.append(name);
                cql.append("'");
            }
            if (StringUtils.isNotEmpty(picType)) {
                cql.append(" and n.picType = ");
                cql.append(picType);
            }
            cql.append(Neo4jUtils.NODE_RETURN);
            Result run = session.run(cql.toString());
            list = Neo4jUtils.getNodeList(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    @Override
    public CaseNode findOneByName(String caseId, String recordId, String nodeType, String name, String picType) {
        CaseNode node = null;
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            Map<String, Object> params = new HashMap<>();
            cql.append("MATCH (n");
            if (StringUtils.isNotEmpty(nodeType)) {
                cql.append(":");
                cql.append(nodeType);
            }
            cql.append(") where 1 = 1");
            if (StringUtils.isNotEmpty(caseId)) {
                cql.append(" and n.caseId = $caseId");
                params.put("caseId", caseId);
            }
            if (StringUtils.isNotEmpty(recordId)) {
                cql.append(" and n.recordId = $recordId");
                params.put("recordId", recordId);
            }
            if (StringUtils.isNotEmpty(name)) {
                cql.append(" and n.name = $name");
                params.put("name", name);
            }
            if (StringUtils.isNotEmpty(picType)) {
                cql.append(" and n.picType = $picType");
                params.put("picType", picType);
            }
            cql.append(Neo4jUtils.NODE_RETURN);
            Result run = session.run(cql.toString(), params);
            node = Neo4jUtils.getOneNode(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return node;
    }

    @Override
    public Rel findRelation(Rel rel) {
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            Map<String, Object> params = new HashMap<>();
            cql.append("MATCH (a)-[rel:").append(rel.getName()).append("]->(b) where id(a) = $sourceId and id(b) = $targetId")
                    .append(Neo4jUtils.REL_RETURN);
            params.put("sourceId", rel.getSourceId());
            params.put("targetId", rel.getTargetId());

            Result run = session.run(cql.toString(), params);
            rel = Neo4jUtils.getOneRel(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (rel != null && rel.getId() != null) {
            return rel;
        } else {
            return null;
        }
    }

    @Override
    public Boolean saveRelation(Rel rel) {
        Rel res = null;
        try {
            Session session = driver.session();
            StringBuffer cql = new StringBuffer();
            Map<String, Object> params = new HashMap<>();
            cql.append("MATCH (a), (b) where id(a) = $sourceId and id(b) = $targetId CREATE(a)-[rel:").append(rel.getName())
                    .append("]->(b) ").append(Neo4jUtils.REL_RETURN);
            params.put("sourceId", rel.getSourceId());
            params.put("targetId", rel.getTargetId());

            Result run = session.run(cql.toString(), params);
            rel = Neo4jUtils.getOneRel(run);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rel != null;
    }

    @Override
    public R<?> getNode(String picType, String caseId) {
        Map<String, Object> map = new HashMap<>();
        List<WebRelDTO> list = new ArrayList<>();
        List<Map<String, Object>> nodes = new ArrayList<>();
        try {
            Session session = driver.session();
            Map<String, Object> params = new HashMap<>();
            params.put("picType", picType);
            params.put("caseId", caseId);
            Result run = session.run("MATCH (n)-[rel]->(r) where n.picType = r.picType = $picType and n.caseId = r.caseId = $caseId" +
                    " RETURN id(rel) as id, n.name as source, id(n) as sourceId, type(rel) as name, r.name as target, id(r) as targetId", params);
            while (run.hasNext()) {
                Record record = run.next();
                //long id = record.get("id").asLong();
                //String source = record.get("source").asString();
                long sourceId = record.get("sourceId").asLong();
                String name = record.get("name").asString();
                //String target = record.get("target").asString();
                long targetId = record.get("targetId").asLong();
                list.add(new WebRelDTO(sourceId, targetId, name));
            }
            Result node = session.run("MATCH (n) where n.picType = $picType and n.caseId = $caseId RETURN id(n) as id, n.name as name", params);
            while (node.hasNext()) {
                Record record = node.next();
                String name = record.get("name").asString();
                long idLong = record.get("id").asLong();
                Map<String, Object> nodeMap = new HashMap<>();
                nodeMap.put("name", name);
                nodeMap.put("entityName", name);
                nodeMap.put("id", idLong);
                nodes.add(nodeMap);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        map.put("list", list);
        map.put("nodes", nodes);
        return R.ok(map);
    }


//    @Override
//    public R<?> test() {
//        Session session = driver.session();
//        Map<String, Object> params = new HashMap<>();
//        params.put("lawActor", "行为人");
//        params.put("lawParty", "aaaaaa");
//        Result run = session.run("MATCH (m:LawActor), (n:FictionalOrgan) where m.name=$lawActor OPTIONAL MATCH (m)-[r:`冒充`]->(n) RETURN id(m) as startId, id(n) as endId, id(r) as relId, m.recordId as recordId, m.recordsId as recordsId", params);
//        while (run.hasNext()) {
//            Record record = run.next();
//
//            String id = Neo4jUtils.valueTransportString(record.get("startId"));
//            String endId = Neo4jUtils.valueTransportString(record.get("endId"));
//            String relId = Neo4jUtils.valueTransportString(record.get("relId"));
//            System.out.println("************" + id);
//            System.out.println("************" + endId);
//            System.out.println("************" + relId);
//        }
//        return R.ok("222");
//    }

    @Override
    public void createAbstractGraph(String path, String sheetName) {
        // 首先从数据库中读到数据
        ExcelReader reader = ExcelUtil.getReader(path, sheetName);
        List<AbstractGraphExcelHeader> abstractGraphExcelHeaders = reader.readAll(AbstractGraphExcelHeader.class);
        Map<String, CaseNode> nodeMap = new HashMap<>();
        Map<String, Rel> relMap = new HashMap<>();
        for (AbstractGraphExcelHeader abstractGraphExcelHeader : abstractGraphExcelHeaders) {
            // from
            if (!nodeMap.containsKey(abstractGraphExcelHeader.getFrom())) {
                CaseNode caseNode = new CaseNode(abstractGraphExcelHeader.getFrom(), abstractGraphExcelHeader.getFrom(), "0");
                log.info("点:{}插入成功", abstractGraphExcelHeader.getFrom());
                CaseNode save = save(caseNode);
                nodeMap.put(abstractGraphExcelHeader.getFrom(), save);
            }
            // to
            if (!nodeMap.containsKey(abstractGraphExcelHeader.getTo())) {
                CaseNode caseNode = new CaseNode(abstractGraphExcelHeader.getTo(), abstractGraphExcelHeader.getTo(), "0");
                CaseNode save = save(caseNode);
                log.info("点:{}插入成功", abstractGraphExcelHeader.getTo());
                nodeMap.put(abstractGraphExcelHeader.getTo(), save);
            }
            // relation
            if (!relMap.containsKey(abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo())) {
                Rel rel = new Rel(nodeMap.get(abstractGraphExcelHeader.getFrom()).getId(), abstractGraphExcelHeader.getRelation(), nodeMap.get(abstractGraphExcelHeader.getTo()).getId(), "0");
                saveRelation(rel);
                log.info("关系:{}插入成功", (abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo()));
                relMap.put(abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo(), rel);
            }
        }


    }

    public void deleteAbstractGraph() {
        Session session = driver.session();
        // 首先查出来所有的抽象节点
        Result run = session.run("MATCH (n) WHERE n.picType = '0' OPTIONAL MATCH (n)-[r]-() RETURN id(n) as nodeId, id(r) as relId");
        Set<String> nodeIdSet = new HashSet<>();
        HashSet<String> relIdSet = new HashSet<>();
        while (run.hasNext()) {
            Record record = run.next();
            String nodeId = Neo4jUtils.valueTransportString(record.get("nodeId"));
            nodeIdSet.add(nodeId);
            String relId = Neo4jUtils.valueTransportString(record.get("relId"));
            relIdSet.add(relId);
        }
        // 删除边
        for (String s : relIdSet) {
            long relId = Long.parseLong(s);
            deleteRel(relId);
            log.info("删除边:{} 成功", relId);
        }
        // 删除节点
        for (String s : nodeIdSet) {
            long nodeId = Long.parseLong(s);
            delNode(nodeId);
            log.info("删除节点:{} 成功", nodeId);
        }


    }

    @Data
    private static class AbstractGraphExcelHeader {
        private String from;
        private String relation;
        private String to;
    }

    @Data
    private static class MockDataGraphExcelHeader {
        private String fromType;
        private String from;
        private String relation;
        private String to;
        private String toType;
    }

    @Override
    public void mockTestGraph(String path, String sheetName, String recordId, String recordSplitId, String caseId) {
        // 首先从数据库中读到数据
        ExcelReader reader = ExcelUtil.getReader(path, sheetName);
        List<MockDataGraphExcelHeader> mockDataGraphExcelList = reader.readAll(MockDataGraphExcelHeader.class);
        Map<String, CaseNode> nodeMap = new HashMap<>();
        Map<String, Rel> relMap = new HashMap<>();
        for (MockDataGraphExcelHeader mockData : mockDataGraphExcelList) {
            // from
            if (!nodeMap.containsKey(mockData.getFrom())) {
                CaseNode caseNode = new CaseNode(mockData.getFrom(), mockData.getFromType(), recordSplitId, recordId, caseId, "1");
                log.info("点:{}插入成功", mockData.getFrom());
                CaseNode save = save(caseNode);
                nodeMap.put(mockData.getFrom(), save);
            }
            // to
            if (!nodeMap.containsKey(mockData.getTo())) {
                CaseNode caseNode = new CaseNode(mockData.getTo(), mockData.getToType(), recordSplitId, recordId, caseId, "1");
                CaseNode save = save(caseNode);
                log.info("点:{}插入成功", mockData.getTo());
                nodeMap.put(mockData.getTo(), save);
            }
            // relation
            if (!relMap.containsKey(mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo())) {
                Rel rel = new Rel(nodeMap.get(mockData.getFrom()).getId(), mockData.getRelation(), nodeMap.get(mockData.getTo()).getId(), "1");
                saveRelation(rel);
                log.info("关系:{}插入成功", (mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo()));
                relMap.put(mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo(), rel);
            }
        }
    }
}