using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Orleans;
using Pole.Orleans.Provider.EntityframeworkCore.Conventions;
using System;
using System.Collections.Generic;
using System.Text;

namespace Pole.Orleans.Provider.EntityframeworkCore
{
    public class GrainStoragePostConfigureOptions<TContext, TGrain, TGrainState, TEntity>
            : IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>
            where TContext : DbContext
            where TGrain : Grain<TGrainState>
            where TGrainState : new()
            where TEntity : class
    {
        public IGrainStorageConvention<TContext, TGrain, TEntity> Convention { get; }
        public IGrainStorageConvention DefaultConvention { get; }

        public GrainStoragePostConfigureOptions(IServiceProvider serviceProvider)
        {
            DefaultConvention =
                (IGrainStorageConvention)serviceProvider.GetRequiredService(typeof(IGrainStorageConvention));
            Convention = (IGrainStorageConvention<TContext, TGrain, TEntity>)
                serviceProvider.GetService(typeof(IGrainStorageConvention<TContext, TGrain, TEntity>));
        }

        public void PostConfigure(string name, GrainStorageOptions<TContext, TGrain, TEntity> options)
        {
            if (!string.Equals(name, typeof(TGrain).FullName))
                throw new Exception("Post configure on wrong grain type.");

            if (options.IsPersistedFunc == null)
                options.IsPersistedFunc =
                    DefaultConvention.CreateIsPersistedFunc<TEntity>(options);

            // Configure ETag
            if (options.ShouldUseETag)
            {
                if (!string.IsNullOrWhiteSpace(options.ETagPropertyName))
                    DefaultConvention.ConfigureETag(options.ETagPropertyName, options);
            }

            if (options.ReadStateAsync == null)
            {
                if (options.DbSetAccessor == null)
                    options.DbSetAccessor = Convention?.CreateDefaultDbSetAccessorFunc()
                                        ?? DefaultConvention.CreateDefaultDbSetAccessorFunc<TContext, TEntity>();

                if (Convention != null)
                    Convention.SetDefaultKeySelector(options);
                else
                    DefaultConvention.SetDefaultKeySelectors(options);

                if (options.PreCompileReadQuery)
                {
                    options.ReadStateAsync
                        = Convention?.CreatePreCompiledDefaultReadStateFunc(options)
                          ?? DefaultConvention
                              .CreatePreCompiledDefaultReadStateFunc(options);
                }
                else
                {
                    options.ReadStateAsync
                        = Convention?.CreateDefaultReadStateFunc()
                          ?? DefaultConvention
                              .CreateDefaultReadStateFunc(options);
                }
            }

            if (options.SetEntity == null)
                options.SetEntity =
                    Convention?.GetSetterFunc()
                    ?? DefaultConvention.GetSetterFunc<TGrainState, TEntity>();

            if (options.GetEntity == null)
                options.GetEntity =
                    Convention?.GetGetterFunc()
                    ?? DefaultConvention.GetGetterFunc<TGrainState, TEntity>();

            DefaultConvention.FindAndConfigureETag(options, options.ShouldUseETag);

            // todo: Validate options

            options.IsConfigured = true;
        }
    }
}