package org.homer.versioner.core.procedure;

import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test;
import org.neo4j.driver.v1.*;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.neo4j.driver.v1.types.Node;
import org.neo4j.harness.junit.Neo4jRule;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.homer.versioner.core.Utility.convertEpochToLocalDateTime;
import static org.junit.Assert.assertThat;

/**
 * UpdateTest class, it contains all the method used to test Update class methods
 */
public class UpdateTest extends GenericProcedureTest {
    @Rule
    public Neo4jRule neo4j = new Neo4jRule()

            // This is the function we want to test
            .withProcedure(Update.class);

    /*------------------------------*/
    /*            update            */
    /*------------------------------*/

    @Test
    public void shouldCreateANewStateWithoutAdditionalLabelAndDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newValue'}) YIELD node RETURN node");
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
        }
    }

    @Test
    public void shouldCreateANewStateWithAdditionalLabelButWithoutDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newValue'}, 'Error') YIELD node RETURN node");
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult currentStateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s) return s");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentStateResult.single().get("s").asNode().hasLabel("Error"), equalTo(true));
        }
    }

    @Test
    public void shouldCreateANewStateWithAdditionalLabelAndDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newValue'}, 'Error', localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult currentStateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s) return s");
            StatementResult dateResult = session.run("MATCH (e:Entity)-[r:CURRENT]->(s) RETURN r.date as relDate");
            StatementResult hasStatusDateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State)-[:PREVIOUS]->(s2:State)<-[rel:HAS_STATE]-(e) RETURN rel.endDate as endDate");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentStateResult.single().get("s").asNode().hasLabel("Error"), equalTo(true));
            assertThat(dateResult.single().get("relDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(hasStatusDateResult.single().get("endDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
        }
    }

    @Test
    public void shouldCreateANewStateFromAnEntityWithoutAState() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newValue'}, 'Error', localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            StatementResult correctResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) RETURN id(s) as stateId");

            // Then
            assertThat(correctResult.single().get("stateId").asLong(), equalTo(20L));
        }
    }

    /*------------------------------*/
    /*             patch            */
	/*------------------------------*/

    @Test
    public void shouldCreateAndPatchANewStateWithoutAdditionalLabelAndDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e, {key:'newValue', newKey:'newestValue'}) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentState.get("key").asString(), equalTo("newValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
        }
    }

    @Test
    public void shouldCreateAndPatchANewStateWithAdditionalLabelButWithoutDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e, {key:'newValue', newKey:'newestValue'}, 'Error') YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult currentStateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s) return s");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentStateResult.single().get("s").asNode().hasLabel("Error"), equalTo(true));
            assertThat(currentState.get("key").asString(), equalTo("newValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
        }
    }

    @Test
    public void shouldCreateAndPatchANewStateWithAdditionalLabelAndDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e, {key:'newValue', newKey:'newestValue'}, 'Error', localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult currentStateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s) return s");
            StatementResult dateResult = session.run("MATCH (e:Entity)-[r:CURRENT]->(s) RETURN r.date as relDate");
            StatementResult hasStatusDateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State)-[:PREVIOUS]->(s2:State)<-[rel:HAS_STATE]-(e) RETURN rel.endDate as endDate");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentStateResult.single().get("s").asNode().hasLabel("Error"), equalTo(true));
            assertThat(dateResult.single().get("relDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(hasStatusDateResult.single().get("endDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(currentState.get("key").asString(), equalTo("newValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
        }
    }

    @Test
    public void shouldCreateAndPatchANewStateWithAdditionalLabelAndDateButWithANewProp() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e, {newKey:'newestValue'}, 'Error', localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult nextResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) return s2");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult currentStateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s) return s");
            StatementResult dateResult = session.run("MATCH (e:Entity)-[r:CURRENT]->(s) RETURN r.date as relDate");
            StatementResult hasStatusDateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State)-[:PREVIOUS]->(s2:State)<-[rel:HAS_STATE]-(e) RETURN rel.endDate as endDate");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(2L));
            assertThat(nextResult.single().get("s2").asNode().id(), equalTo(1L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentStateResult.single().get("s").asNode().hasLabel("Error"), equalTo(true));
            assertThat(dateResult.single().get("relDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(hasStatusDateResult.single().get("endDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(currentState.get("key").asString(), equalTo("initialValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
        }
    }

    @Test
    public void shouldCreateANewStateFromAnEntityWithoutAStateUsingPatch() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})");

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e, {key:'newValue'}, 'Error', localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            StatementResult correctResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) RETURN id(s) as stateId");

            // Then
            assertThat(correctResult.single().get("stateId").asLong(), equalTo(20L));
        }
    }

    @Test
    public void shouldCreateACopyOfTheCurrentStateIfPatchedWithoutStateProps() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");
            StatementResult stateResult = session.run("MATCH (s:State) RETURN s");
            Node state = stateResult.single().get("s").asNode();

            // When
            StatementResult result = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.patch(e) YIELD node RETURN node");

            // Then
            Node newState = result.single().get("node").asNode();
            assertThat(state.get("key"), equalTo(newState.get("key")));
            assertThat(state.size(), equalTo(newState.size()));
        }
    }

    @Test
    public void shouldMaintainTheRelationshipsFromTheCurrentState() throws Throwable {

        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {

            // Given
            Node entityA = initEntity(session);
            Node entityB = initEntity(session);

            String createEntityRelationshipQuery = "MATCH (a:Entity)-[:CURRENT]->(s: State), (b:Entity)<-[:FOR]-(r:R) WHERE id(a) = %d AND id(b) = %d CREATE (s)-[:testType]->(r)";
            session.run(String.format(createEntityRelationshipQuery, entityA.id(), entityB.id()));

            // When
            String patchQuery = "MATCH (e:Entity) WHERE id(e) = %d WITH e CALL graph.versioner.patch(e) YIELD node RETURN node";
            StatementResult result = session.run(String.format(patchQuery, entityA.id()));

            // Then
            Assertions.assertThat(session.run(String.format("MATCH (a:Entity)-[:CURRENT]->(:State)-[:testType]->(:R)-[:FOR]->(b:Entity) WHERE id(a) = %d AND id(b) = %d RETURN a", entityA.id(), entityB.id())))
                    .hasSize(1);
        }
    }

    /*------------------------------*/
    /*          patch.from          */
	/*------------------------------*/

	@Test
    public void shouldCreateACopyOfTheGivenStateWithoutAdditionalDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue', newKey:'oldestValue'})");
            session.run("MATCH (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'}) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");
            session.run("MATCH (e:Entity) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-26T00:00:00'), endDate:localdatetime('1988-10-27T00:00:00')}]->(s:State:Test {newKey:'newestValue'})");
            session.run("MATCH (sc:State)<-[:CURRENT]-(e:Entity)-[:HAS_STATE]->(s:Test) CREATE (sc)-[:PREVIOUS {date:localdatetime('1988-10-26T00:00:00')}]->(s)");
            StatementResult stateResult = session.run("MATCH (s:Test) RETURN s");
            Node originalState = stateResult.single().get("s").asNode();

            // When
            StatementResult result = session.run("MATCH (e:Entity)-[:HAS_STATE]->(s:Test) WITH e, s CALL graph.versioner.patch.from(e, s) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(3L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentState.get("key").asString(), equalTo("initialValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
        }
    }

    @Test
    public void shouldCreateACopyOfTheGivenStateWithAdditionalDate() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue', newKey:'oldestValue'})");
            session.run("MATCH (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'}) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");
            session.run("MATCH (e:Entity) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-26T00:00:00'), endDate:localdatetime('1988-10-27T00:00:00')}]->(s:State:Test {newKey:'newestValue'})");
            session.run("MATCH (sc:State)<-[:CURRENT]-(e:Entity)-[:HAS_STATE]->(s:Test) CREATE (sc)-[:PREVIOUS {date:localdatetime('1988-10-26T00:00:00')}]->(s)");
            StatementResult stateResult = session.run("MATCH (s:Test) RETURN s");
            Node originalState = stateResult.single().get("s").asNode();

            // When
            StatementResult result = session.run("MATCH (e:Entity)-[:HAS_STATE]->(s:Test) WITH e, s CALL graph.versioner.patch.from(e, s, false, localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult dateResult = session.run("MATCH (e:Entity)-[r:CURRENT]->(s) RETURN r.date as relDate");
            StatementResult hasStatusDateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State)-[:PREVIOUS]->(s2:State)<-[rel:HAS_STATE]-(e) RETURN rel.endDate as endDate");

            // Then
            assertThat(countStateResult.single().get("s").asLong(), equalTo(3L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentState.get("key").asString(), equalTo("initialValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
            assertThat(dateResult.single().get("relDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(hasStatusDateResult.single().get("endDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
        }
    }

    @Test
    public void shouldCreateACopyOfTheGivenStateWithAdditionalDateButWithANewProp() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'}) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");
            session.run("MATCH (e:Entity) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-26T00:00:00'), endDate:localdatetime('1988-10-27T00:00:00')}]->(s:State:Test {newKey:'newestValue'})");
            session.run("MATCH (sc:State)<-[:CURRENT]-(e:Entity)-[:HAS_STATE]->(s:Test) CREATE (sc)-[:PREVIOUS {date:localdatetime('1988-10-26T00:00:00')}]->(s)");
            StatementResult stateResult = session.run("MATCH (s:Test) RETURN s");
            Node originalState = stateResult.single().get("s").asNode();

            // When
            StatementResult result = session.run("MATCH (e:Entity)-[:HAS_STATE]->(s:Test) WITH e, s CALL graph.versioner.patch.from(e, s, false,localdatetime('1988-10-27T02:46:40')) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
            StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(s) as s");
            StatementResult correctStateResult = session.run("MATCH (s1:State)-[:PREVIOUS]->(s2:State) WITH s1 MATCH (e:Entity)-[:CURRENT]->(s1) return e");
            StatementResult dateResult = session.run("MATCH (e:Entity)-[r:CURRENT]->(s) RETURN r.date as relDate");
            StatementResult hasStatusDateResult = session.run("MATCH (e:Entity)-[:CURRENT]->(s:State)-[:PREVIOUS]->(s2:State)<-[rel:HAS_STATE]-(e) RETURN rel.endDate as endDate");

            // Then
            //assertThat(currentState.id(), equalTo(21L));
            assertThat(countStateResult.single().get("s").asLong(), equalTo(3L));
            assertThat(correctStateResult.single().get("e").asNode().id(), equalTo(0L));
            assertThat(currentState.get("key").asString(), equalTo("initialValue"));
            assertThat(currentState.get("newKey").asString(), equalTo("newestValue"));
            assertThat(dateResult.single().get("relDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
            assertThat(hasStatusDateResult.single().get("endDate").asLocalDateTime(), equalTo(convertEpochToLocalDateTime(593920000000L)));
        }
    }

    @Test (expected = ClientException.class)
    public void shouldNotCreateACopyOfTheGivenStateSinceItsADifferentEntityState() throws Throwable {
        // This is in a try-block, to make sure we close the driver after the test
        try (Driver driver = GraphDatabase
                .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) {
            // Given
            session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'}) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");
            session.run("MATCH (e:Entity) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-26T00:00:00'), endDate:localdatetime('1988-10-27T00:00:00')}]->(s:State:Test {key:'initialValue'})");
            session.run("MATCH (sc:State)<-[:CURRENT]-(e:Entity)-[:HAS_STATE]->(s:Test) CREATE (sc)-[:PREVIOUS {date:localdatetime('1988-10-26T00:00:00')}]->(s)");
            session.run("CREATE (e:EntityBis {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})");
            session.run("MATCH (e:EntityBis)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)");

            StatementResult result = session.run("MATCH (e:Entity), (:EntityBis)-[:HAS_STATE]->(s:State) WITH e, s CALL graph.versioner.patch.from(e, s) YIELD node RETURN node");
            Node currentState = result.single().get("node").asNode();
        }
    }
}