package org.diagbot.graph.jdbc;

import org.apache.commons.lang3.StringUtils;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.types.Path;

import java.util.*;

public class Neo4jJdbc<T> {
    private final String FROM_NAME = "DIAGBOT_FROM";
    private final String TO_NAME = "DIAGBOT_TO";

    public List<Path> find(String fromLabelName, String toLabelName, String relationName) {
        return this.find(fromLabelName, toLabelName, relationName, null, null);
    }

    public List<Path> find(String fromLabelName, String toLabelName, String relationName,
                           Map<String, Object> fromTitleMap, Map<String, Object> toTitleMap) {
        if (fromTitleMap == null) {
            fromTitleMap = new HashMap<>(10, 0.1f);
        }
        if (toTitleMap == null) {
            toTitleMap = new HashMap<>(10, 0.1f);
        }
        if (StringUtils.isEmpty(fromLabelName) && StringUtils.isEmpty(toLabelName)
                && StringUtils.isEmpty(relationName) && fromTitleMap.isEmpty() && toTitleMap.isEmpty()) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        sb.append("match p=(");

        joinQuery(sb, fromLabelName, fromTitleMap);
        sb.append("-");
        if (StringUtils.isEmpty(relationName)) {
            sb.append("[]");
        } else if (relationName.indexOf("*..") > -1) {
            sb.append("[" + relationName + "]");
        } else {
            sb.append("[r:" + relationName + "]");
        }
        sb.append("->");
        joinQuery(sb, toLabelName, toTitleMap);
        sb.append(") return p");

        System.out.println(sb.toString());

        List<Path> resultList = executeQuery(sb.toString());
        return resultList;
    }

    public List<Path> findNode(String labelName, Map<String, Object> titleMap) {
        if (titleMap == null) {
            titleMap = new HashMap<>(10, 0.1f);
        }
        StringBuffer sb = new StringBuffer();
        sb.append("match p=(");
        joinQuery(sb, labelName, titleMap);
        sb.append(") return p");

        System.out.println(sb.toString());
        List<Path> resultList = executeQuery(sb.toString());
        return resultList;
    }

    public void save(T obj) throws Exception {
        this.save(obj, FROM_NAME, TO_NAME);
    }

    public void save(T obj, String fromLabelName, String toLabelName) throws Exception {
        Neo4jUtil util = new Neo4jUtil();
        Neo4jModel model = util.createModel(obj);
        //三元组已存在，直接返回
        List<Path> paths = find(fromLabelName, toLabelName, model.getRelationName(), model.getFromProperties(), model.getToProperties());
        if (paths != null && paths.size() > 0) {
            return;
        }

        boolean fromNodeNull = false;
        boolean toNodeNull = false;
        //查找fromLabelName节点
        List<Path> fromPaths = findNode(fromLabelName, model.getFromProperties());
        if (fromPaths == null || fromPaths.isEmpty()) {
            fromNodeNull = true;
        }
        List<Path> toPaths = findNode(toLabelName, model.getToProperties());
        if (toPaths == null || toPaths.isEmpty()) {
            toNodeNull = true;
        }
        if (fromNodeNull && toNodeNull) {                   //三元组两个节点都不存在
            this.execute(joinMerge(model, fromLabelName, toLabelName));
        } else if (!fromNodeNull && !toNodeNull) {         //三元组两个节点都存在，增加关系
            this.execute("MATCH(a:" + fromLabelName + "),(b:" + toLabelName + ") WHERE ID(a)=" + fromPaths.get(0).start().id()
                    + " AND ID(b)= " + toPaths.get(0).start().id() + " CREATE (a)-[:" + model.getRelationName() + "]->(b)");
        } else if (!fromNodeNull) {                        //有叶子
            this.execute("MATCH(a:" + fromLabelName + ") WHERE ID(a)=" + fromPaths.get(0).start().id()
                    + " CREATE (a)-[r:" + model.getRelationName() + "]->(b:" + toLabelName + "{" + joinProperties("", model.getToProperties().entrySet()) + "})");
        } else if (!toNodeNull) {
            this.execute("MATCH(b:" + toLabelName + ") WHERE ID(b)=" + toPaths.get(0).start().id()
                    + " CREATE (a:" + fromLabelName + "{" + joinProperties("", model.getFromProperties().entrySet()) + "})-[r:" + model.getRelationName() + "]->(b)");
        }
    }

    public void delete(String fromLabelName, String relationName, String toLabelName) {
        this.execute("match(f:" + fromLabelName + ") - [r:" + relationName + "] ->(t:" + toLabelName + ")detach delete f, r, t");
    }

    public void deleteByLabel(String labelName) {
        this.execute("match(t:" + labelName + ") detach delete t");
    }

    public void deleteByTitle(String labelName, String titleName, String titleValue) {
        this.execute("match(t:" + labelName + "{" + titleName + ":\"" + titleValue + "\"}) detach delete t");
    }

    public void execute(String neo4j_sql) {
        Driver driver = null;
        Session session = null;
        try {
            driver = DriverManager.newDrive();
            session = driver.session();
            session.run(neo4j_sql);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
            if (driver != null) {
                driver.close();
            }
        }
    }

    public List<Path> executeQuery(String sql) {
        List<Path> resultList = new ArrayList<>();
        Driver driver = null;
        Session session = null;
        try {
            driver = DriverManager.newDrive();
            session = driver.session();
            StatementResult result = session.run(sql);

            while (result.hasNext()) {
                Record record = result.next();
                resultList.add(record.get(0).asPath());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
            if (driver != null) {
                driver.close();
            }
        }

        return resultList;
    }

    private StringBuffer joinQuery(StringBuffer sb, String lableName, Map<String, Object> titleMap) {
        if (StringUtils.isEmpty(lableName)) {
            if (titleMap.isEmpty()) {
                sb.append("()");
            } else {
                sb.append("({");
            }
        } else {
            if (titleMap.isEmpty()) {
                sb.append("(" + lableName + ")");
            } else {
                sb.append("(" + lableName + "{");
            }
        }

        if (!titleMap.isEmpty()) {
            int i = 0;
            for (Map.Entry<String, Object> entry : titleMap.entrySet()) {
                if (i == 0) {
                    sb.append(entry.getKey() + ":'" + entry.getValue() + "'");
                } else {
                    sb.append("," + entry.getKey() + ":'" + entry.getValue() + "'");
                }
                i++;
            }
            sb.append("})");
        }
        return sb;
    }

    private String joinMerge(Neo4jModel model, String fromLabelName, String toLabelName) {
        String merge = "MERGE (t:" + toLabelName + "{";
        merge = joinProperties(merge, model.getToProperties().entrySet());
        merge = merge + "})<-[r:" + model.getRelationName() + "]-(l:" + fromLabelName + "{";
        merge = joinProperties(merge, model.getFromProperties().entrySet());
        merge = merge + "})";

        System.out.println(merge);
        return merge;
    }

    private String joinProperties(String merge, Set<Map.Entry<String, Object>> sets) {
        int i = 0;
        for (Map.Entry<String, Object> entry : sets) {
            if (i == 0) {
                merge = merge + entry.getKey() + ":'" + String.valueOf(entry.getValue()) + "'";
            } else {
                merge = merge + "," + entry.getKey() + ":'" + String.valueOf(entry.getValue()) + "'";
            }
            i++;
        }
        return merge;
    }
}
