package casia.isiteam.zdr.neo4j.index;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.RuleChain;
import org.junit.rules.Timeout;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.factory.GraphDatabaseBuilder;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.harness.junit.Neo4jRule;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.test.rule.CleanupRule;
import org.neo4j.test.rule.TestDirectory;
import org.neo4j.test.rule.VerboseTimeout;
import org.neo4j.test.rule.fs.DefaultFileSystemRule;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.lang.String.format;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
 *         ┏┓       ┏┓+ +
 *        ┏┛┻━━━━━━━┛┻┓ + +
 *        ┃       ┃
 *        ┃   ━   ┃ ++ + + +
 *        █████━█████  ┃+
 *        ┃       ┃ +
 *        ┃   ┻   ┃
 *        ┃       ┃ + +
 *        ┗━━┓    ┏━┛
 * ┃    ┃
 *          ┃    ┃ + + + +
 *          ┃   ┃ Code is far away from     bug with the animal protecting
 *          ┃   ┃ +
 *          ┃   ┃
 *          ┃   ┃  +
 *          ┃    ┗━━━┓ + +
 *          ┃      ┣┓
 *          ┃      ┏┛
 *          ┗┓┓┏━━━┳┓┏┛ + + + +
 *           ┃┫┫  ┃┫┫
 *           ┗┻┛  ┗┻┛+ + + +
 */

/**
 * @author YanchaoMa [email protected]
 * @PACKAGE_NAME: casia.isiteam.zdr.neo4j.index
 * @Description: TODO(测试自定义得分计算)
 * @date 2019/8/16 14:49
 */
public class CustomizeFullTextSearcherTest {

    @Rule
    public Neo4jRule neo4j = new Neo4jRule().withProcedure(CustomizeFullTextSearcher.class);

//    static final String QUERY_NODES = "CALL db.index.fulltext.queryNodesBySimHash(\"%s\", \"%s\")";
    static final String QUERY_NODES = "CALL db.index.fulltext.queryNodes(\"%s\", \"%s\")";

    static final String NODE_CREATE = "CALL db.index.fulltext.createNodeIndex(\"%s\", %s, %s )";

    private GraphDatabaseAPI db;
    private GraphDatabaseBuilder builder;

    private final Timeout timeout = VerboseTimeout.builder().withTimeout(1, TimeUnit.HOURS).build();
    private final DefaultFileSystemRule fs = new DefaultFileSystemRule();
    private final TestDirectory testDirectory = TestDirectory.testDirectory();
    private final ExpectedException expectedException = ExpectedException.none();
    private final CleanupRule cleanup = new CleanupRule();

    private static final Label LABEL = Label.label("Label");
    private static final String PROP = "prop";
    static final String NODE = "node";
    static final String RELATIONSHIP = "relationship";
    private static final String SCORE = "score";

    @Rule
    public final RuleChain rules = RuleChain.outerRule(timeout).around(fs).around(testDirectory).around(expectedException).around(cleanup);


    @Before
    public void before() {
        GraphDatabaseFactory factory = new GraphDatabaseFactory();
        builder = factory.newEmbeddedDatabaseBuilder(testDirectory.databaseDir());
        builder.setConfig(GraphDatabaseSettings.store_internal_log_level, "DEBUG");
    }

    @After
    public void tearDown() {
        if (db != null) {
            db.shutdown();
        }
    }

    private GraphDatabaseAPI createDatabase() {
        return (GraphDatabaseAPI) cleanup.add(builder.newGraphDatabase());
    }

    static String array(String... args) {
        return Arrays.stream(args).map(s -> "\"" + s + "\"").collect(Collectors.joining(", ", "[", "]"));
    }

    private void awaitIndexesOnline() {
        try (Transaction tx = db.beginTx()) {
            db.schema().awaitIndexesOnline(1, TimeUnit.MINUTES);
            tx.success();
        }
    }

    /**
     * @param
     * @return
     * @Description: TODO(测试SimHash算法)
     */
    @Test
    public void queryFulltextForNodesBySimHash() {

        db = createDatabase();

        try (Transaction tx = db.beginTx()) {
            db.execute(format(NODE_CREATE, "nodes", array(LABEL.name()), array("prop1", "prop2"))).close();
            tx.success();
        }
        long nodeId;
        try (Transaction tx = db.beginTx()) {
            Node node = db.createNode(LABEL);
            nodeId = node.getId();
            node.setProperty("prop1", "foo");
            node.setProperty("prop2", "bar");
            tx.success();
        }

        awaitIndexesOnline();

        try (Transaction tx = db.beginTx()) {
            Node node = db.getNodeById(nodeId);
            node.setProperty("prop2", 42);
            tx.success();
        }

        try (Transaction tx = db.beginTx()) {
            assertQueryFindsIds( db, true, "nodes", "foo", nodeId );
            Result result = db.execute(format(QUERY_NODES, "nodes", "bar"));
            while (result.hasNext()) {
                Map<String, Object> map = result.next();
                for (Map.Entry entry : map.entrySet()) {
                    System.out.println(entry.getKey() + " " + entry.getValue());
                }
            }
            result.close();
            tx.success();
        }
    }

    /**
     * @param
     * @return
     * @Description: TODO(测试SimHash算法)
     */
    @Test
    public void queryFulltextForNodesBySimHash_2() {
        db = (GraphDatabaseAPI) neo4j.getGraphDatabaseService();

        try (Transaction tx = db.beginTx()) {
            db.execute(format(NODE_CREATE, "nodes", array(LABEL.name()), array("prop1", "prop2"))).close();
            tx.success();
        }
        long nodeId;
        try (Transaction tx = db.beginTx()) {
            Node node = db.createNode(LABEL);
            nodeId = node.getId();
            node.setProperty("prop1", "foo");
            node.setProperty("prop2", "bar");
            tx.success();
        }

        awaitIndexesOnline();

        try (Transaction tx = db.beginTx()) {
            Node node = db.getNodeById(nodeId);
            node.setProperty("prop2", 42);
            System.out.println(node.getId());
            System.out.println(node.getProperty("prop1"));
            tx.success();
        }

        try (Transaction tx = db.beginTx()) {
            assertQueryFindsIds( db, true, "nodes", "foo", nodeId );
            Result result = db.execute(format(QUERY_NODES, "nodes", "bar"));
            while (result.hasNext()) {
                Map<String, Object> map = result.next();
                for (Map.Entry entry : map.entrySet()) {
                    System.out.println(entry.getKey() + " " + entry.getValue());
                }
            }
            result.close();
            tx.success();
        }
    }

    static void assertQueryFindsIds( GraphDatabaseService db, boolean queryNodes, String index, String query, long... ids )
    {
        try ( Transaction tx = db.beginTx() )
        {
            String queryCall = queryNodes ? QUERY_NODES : null;
            Result result = db.execute( format( queryCall, index, query ) );
            int num = 0;
            Double score = Double.MAX_VALUE;
            while ( result.hasNext() )
            {
                Map entry = result.next();
                Long nextId = ((Entity) entry.get( queryNodes ? NODE : RELATIONSHIP )).getId();
                Double nextScore = (Double) entry.get( SCORE );
//                assertThat( nextScore, lessThanOrEqualTo( score ) );
                score = nextScore;
                if ( num < ids.length )
                {
                    assertEquals( format( "Result returned id %d, expected %d", nextId, ids[num] ), ids[num], nextId.longValue() );
                }
                else
                {
                    fail( format( "Result returned id %d, which is beyond the number of ids (%d) that were expected.", nextId, ids.length ) );
                }
                num++;
            }
            assertEquals( "Number of results differ from expected", ids.length, num );
            tx.success();
        }
    }

}