using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Luticate2.Auth.Utils.Business.ExpressionConverter;
using Luticate2.Auth.Utils.Business.ObjectConverterDescriptor;
using Luticate2.Auth.Utils.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Luticate2.Auth.Tests.Business.ObjectConverter
{
    public class ObjectConverterTests
    {

        protected IServiceProvider GetServiceProvider()
        {
            var typeILuObjectConverterDescriptor = typeof(ILuObjectConverterDescriptor<,>);
            var typeILuObjectConverterDescriptorEnumerable =
                typeILuObjectConverterDescriptor.MakeGenericType(typeof(Enumerable), typeof(Enumerable));

            var services = new ServiceCollection();
            services.AddSingleton<ILuObjectConverterDescriptorIdentity, LuObjectConverterDescriptorIdentity>();
            services.AddSingleton(typeILuObjectConverterDescriptorEnumerable, typeof(LuObjectConverterDescriptorEnumerable));
            services.AddSingleton<ILuObjectConverterDescriptor<TestDbo1, TestModel1>, LuOcdTest1>();
            services.AddSingleton<ILuObjectConverterDescriptor<TestDbo2, TestModel2>, LuOcdTest2>();

            var serviceProvider = services.BuildServiceProvider();
            return serviceProvider;
        }

        protected LuExpressionConverterOptions GetConverterOptions()
        {
            var options = new LuExpressionConverterOptions
            {
                Parameters = new Dictionary<ParameterExpression, Expression>(),
                Types = new Dictionary<Type, Type>
                {
                    {typeof(TestDbo1), typeof(TestModel1)},
                    {typeof(TestDbo2), typeof(TestModel2)}
                }
            };
            return options;
        }

        protected LuExpressionConverterVisitor GetConverter()
        {
            return new LuExpressionConverterVisitor(GetConverterOptions(), GetServiceProvider());
        }

        [Fact]
        public void TestSimpleProperty1()
        {
            Expression<Func<TestDbo2, Guid>> expDbo = (x => x.TestDbo1.Id);
            Expression<Func<TestModel2, Guid>> expModel = (Param_0 => Param_0.test_model1.id);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleProperty2()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => x.TestDbo1.Id == Guid.Empty);
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => Param_0.test_model1.id == Guid.Empty);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleProperty3()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => x.TestDbo1.Name.Length == 2);
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => Param_0.test_model1.name.Length == 2);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestStaticProperty1()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => (x.TestDbo1.Name == "42") == StaticDbo.StaticField);
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => (Param_0.test_model1.name == "42") == StaticDbo.StaticField);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestStaticProperty2()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => (x.TestDbo1.Name == "42") == StaticDbo.StaticProperty);
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => (Param_0.test_model1.name == "42") == StaticDbo.StaticProperty);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestVirtualProperty1()
        {
            Expression<Func<TestDbo2, string>> expDbo = (x => x.NameVirtual);
            Expression<Func<TestModel2, string>> expModel = (Param_0 => Param_0.name + " " + Param_0.name);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestVirtualProperty2()
        {
            Expression<Func<TestDbo2, string>> expDbo = (x => x.Parent.NameVirtual);
            Expression<Func<TestModel2, string>> expModel = (Param_0 => Param_0.parent.name + " " + Param_0.parent.name);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestStaticValue1()
        {
            Expression<Func<TestDbo2, int>> expDbo = (x => 0);
            Expression<Func<TestModel2, int>> expModel = (Param_0 => 0);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestStaticValue2()
        {
            Expression<Func<TestDbo2, TestDbo2>> expDbo = (x => x);
            Expression<Func<TestModel2, TestModel2>> expModel = (Param_0 => Param_0);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestStaticValue3()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => StaticDbo.StaticField);
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => StaticDbo.StaticField);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestComplexExpression1()
        {
            Expression<Func<TestDbo2, int>> expDbo = (x => (x.Name + " " + x.Name).Length);
            Expression<Func<TestModel2, int>> expModel = (Param_0 => (Param_0.name + " " + Param_0.name).Length);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestComplexExpression2()
        {
            Expression<Func<TestDbo2, int>> expDbo = (x => (x.NameVirtual + " " + x.Name).Length);
            Expression<Func<TestModel2, int>> expModel = (Param_0 => (Param_0.name + " " + Param_0.name + " " + Param_0.name).Length);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleMethodSimpleArg1()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => x.Name.Contains("s"));
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => Param_0.name.Contains("s"));
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleMethodSimpleArg2()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => x.TestDbo1.Name.Contains("s"));
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => Param_0.test_model1.name.Contains("s"));
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleMethodSimpleArg3()
        {
            Expression<Func<TestDbo2, string>> expDbo = (x => x.TestDbo1.ToString());
            Expression<Func<TestModel2, string>> expModel = (Param_0 => Param_0.test_model1.id + ": " + Param_0.test_model1.name);
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestSimpleMethodAdvArg1()
        {
            Expression<Func<TestDbo2, bool>> expDbo = (x => x.TestDbo1.Name.Contains(x.Name));
            Expression<Func<TestModel2, bool>> expModel = (Param_0 => Param_0.test_model1.name.Contains(Param_0.name));
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestAdvMethodSimpleArg1()
        {
            Expression<Func<TestDbo1, bool>> expDbo = (x => x.TestDbo2s.Any());
            Expression<Func<TestModel1, bool>> expModel = (Param_0 => Param_0.test_model2.Any());
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }

        [Fact]
        public void TestAdvMethodAdvArg1()
        {
            Expression<Func<TestDbo1, bool>> expDbo = (x => x.TestDbo2s.Any(y => y.Name.Contains(x.Name + "s")));
            Expression<Func<TestModel1, bool>> expModel = (Param_0 => Param_0.test_model2.Any(Param_1 => Param_1.name.Contains(Param_0.name + "s")));
            var converter = GetConverter();
            var result = converter.Visit(expDbo);
            Assert.Equal(expModel.ToString(), result?.ToString());
        }
    }
}