#region License

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#endregion

using Gremlin.Net.Driver;
using Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection;
using Gremlin.Net.Process.Traversal;
using Gremlin.Net.Structure;
using Gremlin.Net.Structure.IO.GraphBinary;
using Gremlin.Net.Structure.IO.GraphSON;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;

namespace Gremlin.Net.IntegrationTest.Driver
{
    public class PropertyDeserializationTests
    {
        private readonly RemoteConnectionFactory _connectionFactory = new();

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldDeserializeVertexPropertiesForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.V(1).Next();

            VerifyVertexProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldRespectMaterializePropertiesTokensForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.With(Tokens.ArgMaterializeProperties, "tokens").V(1).Next();

            VerifyEmptyProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldRespectMaterializePropertiesAllForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.With(Tokens.ArgMaterializeProperties, "all").V(1).Next();

            VerifyVertexProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldHandleEmptyVertexPropertiesForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.AddV("test").Next();

            VerifyEmptyProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldDeserializeVertexPropertiesForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.V(1)");

            VerifyVertexProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldHandleEmptyVertexPropertiesForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gimmutable.addV('test')");

            VerifyEmptyProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldRespectMaterializePropertiesAllForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.with('materializeProperties', 'all').V(1)");

            VerifyVertexProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldRespectMaterializePropertiesTokensForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gmodern.with('materializeProperties', 'tokens').V(1)");

            VerifyEmptyProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldDeserializeEdgePropertiesForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gmodern", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var edge = g.E(7).Next();

            VerifyEdgeProperties(edge);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldHandleEmptyEdgePropertiesForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var v1 = g.AddV("v1").Next();
            var v2 = g.AddV("v2").Next();
            var edge = g.AddE("test").From(v1).To(v2).Next();

            VerifyEmptyProperties(edge);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldDeserializeEdgePropertiesForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var edge = await client.SubmitWithSingleResultAsync<Edge>("gmodern.E(7)");

            VerifyEdgeProperties(edge);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldHandleEmptyEdgePropertiesForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var edge = await client.SubmitWithSingleResultAsync<Edge>(
                "gimmutable.addV().as('v1').addV().as('v2').addE('test').from('v1').to('v2')");

            VerifyEmptyProperties(edge);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldHandleMultiplePropertiesWithSameNameForVertex(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gimmutable", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.AddV()
                .Property(Cardinality.List, "test", "value1")
                .Property(Cardinality.List, "test", "value2")
                .Property(Cardinality.List, "test", "value3")
                .Next()!;

            vertex = g.V(vertex.Id).Next();

            Assert.NotNull(vertex);

            var properties = vertex.Properties!;
            Assert.Equal(3, properties.Length);
            var propertyValues = properties.Cast<VertexProperty>().Where(p => p.Key == "test").Select(p => p.Value).ToArray();
            Assert.Equal(3, propertyValues.Length);
            Assert.Equal(new[] { "value1", "value2", "value3" }, propertyValues);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public void ShouldDeserializeVertexVertexPropertiesForBytecode(IMessageSerializer serializer)
        {
            var connection = _connectionFactory.CreateRemoteConnection("gcrew", 2, serializer);
            var g = AnonymousTraversalSource.Traversal().WithRemote(connection);

            var vertex = g.V(7).Next();

            VerifyVertexVertexProperties(vertex);
        }

        [Theory]
        [MemberData(nameof(Serializers))]
        public async Task ShouldDeserializeVertexVertexPropertiesForGremlin(IMessageSerializer serializer)
        {
            var client = _connectionFactory.CreateClient(serializer);

            var vertex = await client.SubmitWithSingleResultAsync<Vertex>("gcrew.V(7)");

            VerifyVertexVertexProperties(vertex);
        }

        private static void VerifyVertexProperties(Vertex? vertex)
        {
            Assert.NotNull(vertex);
            Assert.Equal(1, vertex.Id);
            Assert.Equal("person", vertex.Label);
            Assert.True(2 == vertex.Properties!.Length, $"Unexpected properties count: {JsonSerializer.Serialize(vertex.Properties)}");

            var age = vertex.Property("age");
            Assert.NotNull(age);
            Assert.Equal(29, age.Value);
        }

        private static void VerifyVertexVertexProperties(Vertex? vertex)
        {
            Assert.NotNull(vertex);
            Assert.Equal(7, vertex.Id);
            Assert.Equal("person", vertex.Label);
            Assert.True(4 == vertex.Properties!.Length, $"Unexpected properties count: {JsonSerializer.Serialize(vertex.Properties)}");

            var locations = vertex.Properties.Cast<VertexProperty>().Where(p => p.Key == "location");
            Assert.NotNull(locations);
            Assert.Equal(3, locations.Count());

            var vertexProperty = locations.First();
            Assert.Equal("centreville", vertexProperty.Value);
            Assert.Equal(2, vertexProperty.Properties!.Length);

            var vertexPropertyPropertyStartTime = vertexProperty.Property("startTime");
            Assert.Equal(1990, vertexPropertyPropertyStartTime!.Value);

            var vertexPropertyPropertyEndTime = vertexProperty.Property("endTime");
            Assert.Equal(2000, vertexPropertyPropertyEndTime!.Value);
        }

        private static void VerifyEdgeProperties(Edge? edge)
        {
            Assert.NotNull(edge);
            Assert.Equal(7, edge.Id);
            Assert.Equal("knows", edge.Label);
            Assert.True(1 == edge.Properties!.Length, $"Unexpected properties count: {JsonSerializer.Serialize(edge.Properties)}");

            var weight = edge.Property("weight");
            Assert.NotNull(weight);
            Assert.Equal(0.5, weight.Value);
        }

        private static void VerifyEmptyProperties(Element? element)
        {
            Assert.NotNull(element);
            Assert.True((element.Properties?.Length ?? 0) == 0);
        }

        public static List<object[]> Serializers => new()
        {
            new [] { new GraphSON2MessageSerializer() },
            new [] { new GraphSON3MessageSerializer() },
            new [] { new GraphBinaryMessageSerializer() }
        };
    }
}
