/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package apoc.path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.neo4j.configuration.SettingImpl.newBuilder;
import static org.neo4j.configuration.SettingValueParsers.BOOL;

import apoc.util.TestUtil;
import com.neo4j.test.extension.EnterpriseDbmsExtension;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.TestDatabaseManagementServiceBuilder;
import org.neo4j.test.extension.ExtensionCallback;
import org.neo4j.test.extension.Inject;

@EnterpriseDbmsExtension(createDatabasePerTest = false, configurationCallback = "configure")
public class LabelSequenceTest {

    @Inject
    GraphDatabaseService db;

    @ExtensionCallback
    void configure(TestDatabaseManagementServiceBuilder builder) {
        builder.setConfig(
                        newBuilder("internal.dbms.debug.track_cursor_close", BOOL, false)
                                .build(),
                        false)
                .setConfig(
                        newBuilder("internal.dbms.debug.trace_cursors", BOOL, false)
                                .build(),
                        false);
    }

    @BeforeAll
    void setUp() {
        TestUtil.registerProcedure(db, PathExplorer.class);
        db.executeTransactionally(
                "create (s:Start{name:'start'})-[:REL]->(:A{name:'a'})-[:REL]->(:B{name:'b'})-[:REL]->(:A:C{name:'ac'})-[:REL]->(:B:A{name:'ba'})-[:REL]->(:D:A{name:'da'})");
    }

    @Test
    void testBasicSequence() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'A,B', beginSequenceAtStart:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(6L, names.size());
            assertTrue(names.containsAll(Arrays.asList("start", "a", "b", "ac", "ba", "da")));
        });
    }

    @Test
    void testNoMatchWhenImproperlyStartingSequenceAtStart() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'A,B', beginSequenceAtStart:true, filterStartNode:true}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(0L, names.size());
        });
    }

    @Test
    void testMatchWhenProperlyStartingSequenceAtStart() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'Start,A,B', beginSequenceAtStart:true, filterStartNode:true}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(3L, names.size());
            assertTrue(names.containsAll(Arrays.asList("start", "a", "b")));
        });
    }

    @Test
    void testSequenceWithDenylist() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'A|-C,B', beginSequenceAtStart:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(3L, names.size());
            assertTrue(names.containsAll(Arrays.asList("start", "a", "b")));
        });
    }

    @Test
    void testSequenceWithTerminatorNode() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'/A|C,B', beginSequenceAtStart:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(1L, names.size());
            assertTrue(names.containsAll(Arrays.asList("a")));
        });
    }

    @Test
    void testSequenceWithEndNode() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'>A|C,B', beginSequenceAtStart:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(3L, names.size());
            assertTrue(names.containsAll(Arrays.asList("a", "ac", "da")));
        });
    }

    @Test
    void testSequenceWithEndNodeAndLimit() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'>A|C,B', beginSequenceAtStart:false, limit:2}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(2L, names.size());
            assertTrue(names.containsAll(Arrays.asList("a", "ac")));
        });
    }

    @Test
    void testSequenceWithTerminatorNodeAsStartNodeWithoutFilteringStartNode() {
        String query =
                "MATCH (a:A {name: 'a'}) CALL apoc.path.subgraphNodes(a,{labelFilter:'/A, B', filterStartNode:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(1L, names.size());
            assertTrue(names.containsAll(Arrays.asList("ac")));
        });
    }

    @Test
    void testSequenceWithTerminatorNodeAndEndNode() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.subgraphNodes(s,{labelFilter:'>A|C|/A:C, B', beginSequenceAtStart:false}) yield node return collect(distinct node.name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(2L, names.size());
            assertTrue(names.containsAll(Arrays.asList("a", "ac")));
        });
    }

    @Test
    void testSequenceWithTerminatorNodeWhenUsingMinLevel() {
        String query =
                "MATCH (s:Start {name: 'start'}) CALL apoc.path.expandConfig(s,{labelFilter:'/A, B', beginSequenceAtStart:false, minLevel:3}) yield path return collect(distinct last(nodes(path)).name) as nodes";
        TestUtil.testCall(db, query, (row) -> {
            List<String> names = (List<String>) row.get("nodes");
            assertEquals(1L, names.size());
            assertTrue(names.containsAll(Arrays.asList("ac")));
        });
    }
}
