using System;
using System.Threading.Tasks;
using Furion.DependencyInjection;
using SqlSugar;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Waste.Domain
{
    public class JT_Setting
    {
        public JT_Setting() { }
        public JT_Setting(Guid id, string name, string value, string providerName, string providerKey)
        {
            this.Id = id;
            this.Name = name;
            this.Value = value;
            this.ProviderName = providerName;
            this.ProviderKey = providerKey;
        }
        [SugarColumn(IsPrimaryKey = true)]
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }

        public string ProviderName { get; set; }
        public string ProviderKey { get; set; }

        public override string ToString()
        {
            return $"{base.ToString()}, Name = {Name}, Value = {Value}, ProviderName = {ProviderName}, ProviderKey = {ProviderKey}";
        }
    }
    public class SettingProvider : ITransient
    {
        private readonly IServiceProvider _serviceProvider;

        private readonly SettingDefinitionManager SettingDefinitionManager;
        private readonly SettingValueProviderManager SettingValueProviderManager;


        public SettingProvider(IServiceProvider serviceProvider, SettingDefinitionManager settingDefinitionManager, SettingValueProviderManager settingValueProviderManager)
        {
            _serviceProvider = serviceProvider;
            SettingDefinitionManager = settingDefinitionManager;
            SettingValueProviderManager = settingValueProviderManager;
        }

        public virtual async Task<string?> GetOrNullAsync(string name)
        {
            var setting = await SettingDefinitionManager.GetAsync(name);
            var providers = Enumerable
                .Reverse(SettingValueProviderManager.Providers);

            if (setting.Providers.Any())
            {
                providers = providers.Where(p => setting.Providers.Contains(p.Name));
            }

            //TODO: How to implement setting.IsInherited?

            var value = await GetOrNullValueFromProvidersAsync(providers, setting);
            /*      if (value != null && setting.IsEncrypted)
                 {
                     value = SettingEncryptionService.Decrypt(setting, value);
                 }*/

            return value;
        }



        protected virtual async Task<string?> GetOrNullValueFromProvidersAsync(
            IEnumerable<SettingValueProvider> providers,
            SettingDefinition setting)
        {
            foreach (var provider in providers)
            {
                var value = await provider.GetOrNullAsync(setting);
                if (value != null)
                {
                    return value;
                }
            }

            return null;
        }
    }

    public class SettingDefinitionManager : ISingleton
    {
        protected IDictionary<string, SettingDefinition> SettingDefinitions { get; } =
            new Dictionary<string, SettingDefinition>();

        public SettingDefinitionManager AddSettingDefinition(SettingDefinition settingDefinition)
        {
            SettingDefinitions[settingDefinition.Name] = settingDefinition;
            return this;
        }
        public virtual async Task<SettingDefinition> GetAsync(string name)
        {

            var setting = await GetOrNullAsync(name);

            if (setting == null)
            {
                throw new Exception("Undefined setting: " + name);
            }

            return setting;
        }
        public virtual Task<SettingDefinition?> GetOrNullAsync(string name)
        {
            return Task.FromResult(GetOrDefault(SettingDefinitions, name));
        }
        public static TValue? GetOrDefault<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key)
        {
            return dictionary.TryGetValue(key, out var obj) ? obj : default;
        }
    }

    public class SettingValueProviderManager : ISingleton
    {
        public List<SettingValueProvider> Providers => _lazyProviders.Value;
        private readonly Lazy<List<SettingValueProvider>> _lazyProviders;
        protected IServiceProvider ServiceProvider { get; }
        public SettingValueProviderManager(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
            _lazyProviders = new Lazy<List<SettingValueProvider>>(GetProviders, true);
        }



        protected virtual List<SettingValueProvider> GetProviders()
        {

            using (var scope = ServiceProvider.CreateScope())

                return new List<SettingValueProvider>()
            {
                scope.ServiceProvider.GetRequiredService<DefaultValueSettingValueProvider>(),
                scope.ServiceProvider.GetRequiredService<GlobalSettingValueProvider>(),
                scope.ServiceProvider.GetRequiredService<UserSettingValueProvider>(),
            };
        }
    }


    public class SettingManagementStore : ITransient
    {
        private readonly ISqlSugarRepository<JT_Setting> repository;

        public SettingManagementStore(ISqlSugarRepository<JT_Setting> repository)
        {
            this.repository = repository;
        }

        public virtual async Task<string> GetOrNullAsync(string name, string providerName, string providerKey)
        {
            return (await FindAsync(name, providerName, providerKey))?.Value;
        }
        public virtual async Task SetAsync(string name, string value, string providerName, string providerKey)
        {
            var setting = await FindAsync(name, providerName, providerKey);
            if (setting == null)
            {
                setting = new JT_Setting(Guid.NewGuid(), name, value, providerName, providerKey);
                await repository.InsertAsync(setting);
            }
            else
            {
                setting.Value = value;
                await repository.UpdateAsync(setting);
            }


        }

        private async Task<JT_Setting> FindAsync(string name, string providerName, string providerKey)
        {
            return await repository.Where(x =>
                x.Name == name && x.ProviderName == providerName)
                .WhereIF(!string.IsNullOrWhiteSpace(providerKey), x => x.ProviderKey == providerKey).FirstAsync();
        }
    }

    public abstract class SettingValueProvider : ITransient
    {
        public abstract string Name { get; }

        protected SettingManagementStore SettingStore { get; }

        protected SettingValueProvider(SettingManagementStore settingStore)
        {
            SettingStore = settingStore;
        }
        public abstract Task<string?> GetOrNullAsync(SettingDefinition setting);

        protected virtual async Task SetAsync(string name, string value, string providerKey)
        {
            await SettingStore.SetAsync(name, value, Name, providerKey);

        }

        protected virtual async Task SetAsync(SettingDefinition setting, string value, string providerKey)
        {
            await SettingStore.SetAsync(setting.Name, value, Name, providerKey);
        }

    }

    public class GlobalSettingValueProvider : SettingValueProvider
    {
        public GlobalSettingValueProvider(SettingManagementStore settingStore) : base(settingStore)
        {
        }

        public const string ProviderName = "G";

        public override string Name => ProviderName;

        public override Task<string> GetOrNullAsync(SettingDefinition setting)
        {
            return SettingStore.GetOrNullAsync(setting.Name, Name, null);
        }
        public Task SetAsync(string name, string value)
        {
            return base.SetAsync(name, value, null);
        }
    }
    public class UserSettingValueProvider : SettingValueProvider
    {
        public const string ProviderName = "U";

        public override string Name => ProviderName;

        protected ICurrentUser CurrentUser { get; }

        public UserSettingValueProvider(SettingManagementStore settingStore, ICurrentUser currentUser)
            : base(settingStore)
        {
            CurrentUser = currentUser;
        }

        public override async Task<string?> GetOrNullAsync(SettingDefinition setting)
        {
            if (CurrentUser.Id == null)
            {
                return null;
            }

            return await SettingStore.GetOrNullAsync(setting.Name, Name, CurrentUser.Id.ToString());
        }

        public Task SetAsync(string name, string value)
        {
            return base.SetAsync(name, value, CurrentUser.Id.ToString());
        }
    }

    public interface ICurrentUser
    {
        Guid? Id { get; }
    }
    public class DefaultValueSettingValueProvider : SettingValueProvider
    {
        public const string ProviderName = "D";

        public override string Name => ProviderName;

        public DefaultValueSettingValueProvider(SettingManagementStore settingStore)
            : base(settingStore)
        {

        }

        public override Task<string?> GetOrNullAsync(SettingDefinition setting)
        {
            return Task.FromResult(setting.DefaultValue);
        }


    }
    //        public Guid UserId { get; set; }

    public class SettingDefinition
    {
        /// <summary>
        /// Unique name of the setting.
        /// </summary>
        [NotNull]
        public string Name { get; set; }

        public string DisplayName { get; set; }

        /// <summary>
        /// Default value of the setting.
        /// </summary>
        public string? DefaultValue { get; set; }
        /// <summary>
        /// A list of allowed providers to get/set value of this setting.
        /// An empty list indicates that all providers are allowed.
        /// </summary>
        public List<string> Providers { get; } = new List<string>();
    }
}