using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Luticate2.Auth.Utils.Business.Converters;
using Luticate2.Auth.Utils.Business.Fields;
using Luticate2.Auth.Utils.Dbo;
using Luticate2.Auth.Utils.Dbo.Fields;
using Luticate2.Auth.Utils.Dbo.Result;
using Luticate2.Auth.Utils.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Luticate2.Auth.Tests.Business.ObjectConverter
{
    public class ObjectConverterTests
    {
        protected IServiceProvider GetServiceProvider()
        {
            var services = new ServiceCollection();
            services.AddLuObjectConverterDescriptors();
            services.AddSingleton<ILuObjectConverterDescriptor<TestDbo1, TestModel1>, LuOcdTest1>();
            services.AddSingleton<ILuObjectConverterDescriptor<TestDbo2, TestModel2>, LuOcdTest2>();

            services.AddLuObjectConverters();
            services.AddLuObjectConverterPoco<TestModel1, TestDbo1>();
            services.AddLuObjectConverterPoco<TestModel2, TestDbo2>();

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

        protected ILuObjectConverterOptions GetConverterOptions(IServiceProvider serviceProvider)
        {
            var options = new LuConvertersOptions
            {
                Parameters = new Dictionary<ParameterExpression, Expression>(),
                TypeConverter = new LuConvertersTypeConverter(new Dictionary<Type, Type>
                {
                    {typeof(TestDbo1), typeof(TestModel1)},
                    {typeof(TestDbo2), typeof(TestModel2)},
                    {typeof(TestModel1), typeof(TestDbo1)},
                    {typeof(TestModel2), typeof(TestDbo2)}
                }),
                Allocator = serviceProvider.GetService<ILuConvertersAllocator>()
            };
            return options;
        }

        protected TestModel1 GetBasicModel()
        {
            return new TestModel1
            {
                id = Guid.NewGuid(),
                name = "Test.",
                test_model2 = new List<TestModel2>
                {
                    new TestModel2
                    {
                        id = Guid.NewGuid(),
                        name = "bla",
                        parent = null,
                        test_model1 = null,
                        test_model1_id = Guid.Empty,
                        optional_int = 42
                    }
                }
            };
        }

        [Fact]
        public void TestBasic1()
        {
            var serviceProvider = GetServiceProvider();
            var converter = serviceProvider.GetService<ILuObjectConverter<TestModel1, TestDbo1>>();
            var model = GetBasicModel();
            var result = converter.Convert(model, new TestDbo1(), new LuFieldDbo(), LuPartialFieldsParser.Parse("*").Data, GetConverterOptions(serviceProvider));
            Assert.Equal(LuStatus.Success.ToInt(), result.Status);
            var dbo = result.Data as TestDbo1;
            Assert.NotNull(dbo);
            Assert.Equal(model.id, dbo.Id);
            Assert.Equal(model.name, dbo.Name);
            Assert.NotNull(dbo.TestDbo2s);
            Assert.Equal(model.test_model2.Count, dbo.TestDbo2s.Count);
            foreach (var e in model.test_model2.Zip(dbo.TestDbo2s, (model2, dbo2) => new {Model = model2, Dbo = dbo2}))
            {
                Assert.Equal(e.Model.id, e.Dbo.Id);
                Assert.Equal(e.Model.name, e.Dbo.Name);
                Assert.Equal(e.Model.name + " " + e.Model.name, e.Dbo.NameVirtual);
                Assert.Equal(e.Model.optional_int, e.Dbo.NotOptionalInt);
                Assert.Null(e.Dbo.Parent);
                Assert.Null(e.Dbo.TestDbo1);
            }
        }

        [Fact]
        void TestPath1()
        {
            var serviceProvider = GetServiceProvider();
            var converter = serviceProvider.GetService<ILuObjectConverter<TestModel1, TestDbo1>>();
            var model = GetBasicModel();
            var result = converter.Convert(model, new TestDbo1(), new LuFieldDbo(), LuPartialFieldsParser.Parse("Id").Data, GetConverterOptions(serviceProvider));
            Assert.Equal(LuStatus.Success.ToInt(), result.Status);
            var dbo = result.Data as TestDbo1;
            Assert.NotNull(dbo);
            Assert.Equal(model.id, dbo.Id);
            Assert.Null(dbo.Name);
            Assert.Null(dbo.TestDbo2s);
        }

        [Fact]
        void TestPath2()
        {
            var serviceProvider = GetServiceProvider();
            var converter = serviceProvider.GetService<ILuObjectConverter<TestModel1, TestDbo1>>();
            var model = GetBasicModel();
            var result = converter.Convert(model, new TestDbo1(), new LuFieldDbo(), LuPartialFieldsParser.Parse("id").Data, GetConverterOptions(serviceProvider));
            Assert.Equal(LuStatus.Success.ToInt(), result.Status);
            var dbo = result.Data as TestDbo1;
            Assert.NotNull(dbo);
            Assert.Equal(model.id, dbo.Id);
            Assert.Null(dbo.Name);
            Assert.Null(dbo.TestDbo2s);
        }

        [Fact]
        public void TestPath3()
        {
            var serviceProvider = GetServiceProvider();
            var converter = serviceProvider.GetService<ILuObjectConverter<TestModel1, TestDbo1>>();
            var model = GetBasicModel();
            var result = converter.Convert(model, new TestDbo1(), new LuFieldDbo(), LuPartialFieldsParser.Parse("Id, TestDbo2s(Id, Name)").Data, GetConverterOptions(serviceProvider));
            Assert.Equal(LuStatus.Success.ToInt(), result.Status);
            var dbo = result.Data as TestDbo1;
            Assert.NotNull(dbo);
            Assert.Equal(model.id, dbo.Id);
            Assert.Null(dbo.Name);
            Assert.NotNull(dbo.TestDbo2s);
            Assert.Equal(model.test_model2.Count, dbo.TestDbo2s.Count);
            foreach (var e in model.test_model2.Zip(dbo.TestDbo2s, (model2, dbo2) => new {Model = model2, Dbo = dbo2}))
            {
                Assert.Equal(e.Model.id, e.Dbo.Id);
                Assert.Equal(e.Model.name, e.Dbo.Name);
                Assert.Null(e.Dbo.NameVirtual);
                Assert.Null(e.Dbo.Parent);
                Assert.Null(e.Dbo.TestDbo1);
            }
        }
    }
}