using System;
using System.Collections.Generic;
using System.Linq;
using InABox.Core;

namespace InABox.Configuration
{
    public interface IGlobalConfigurationSettings : IConfigurationSettings
    {
    }

    [UserTracking(false)]
    [Unrecoverable]
    public class GlobalSettings : Entity, IPersistent, IRemotable, ILicense<CoreLicense>
    {
        public string Section { get; set; } = "";

        public string Key { get; set; } = "";

        public string Contents { get; set; } = "";
    }

    public interface IGlobalConfiguration
    {
        string[] Sections();
        object Load(bool useCache = true);
        void Save(object obj);
        void Delete(Action<Exception?>? callback = null);
    }

    public class GlobalConfiguration<T> : Configuration<T>, IGlobalConfiguration where T : IGlobalConfigurationSettings, new()
    {
        private IConfigurationProvider<GlobalSettings> Provider;

        public GlobalConfiguration(string section = "") : this(section, ClientConfigurationProvider<GlobalSettings>.Provider)
        {
        }

        public GlobalConfiguration(string section, IConfigurationProvider<GlobalSettings> provider) : base(section)
        {
            Provider = provider;
        }

        private GlobalSettings NewSettings(string? section = null)
        {
            return new GlobalSettings { Section = typeof(T).Name, Key = section ?? Section };
        }

        private GlobalSettings GetSettings()
        {
            GlobalSettings? result = null;
            result = Provider.Load(
                    new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name).And(x => x.Key).IsEqualTo(Section))
                .FirstOrDefault();
            return result ?? NewSettings();
        }

        object IGlobalConfiguration.Load(bool useCache) => Load(useCache);


        public override T Load(bool useCache = true)
        {
            if (useCache)
            {
                var cached = ConfigurationCache.Check<T>(ConfigurationCacheType.Global, Section);
                if (cached != null)
                    //Logger.Send(LogType.Information, "", "Global Config: Returning Cached Configuration Data");
                    return cached;
            }

            var result = new T();

            var setting = GetSettings();

            if (!string.IsNullOrEmpty(setting.Contents))
            {
                result = Serialization.Deserialize<T>(setting.Contents);
                if(result is BaseObject obj)
                {
                    obj.CommitChanges();
                }
            }

            ConfigurationCache.Add(ConfigurationCacheType.Global, Section, result);
            return result;
        }

        public override Dictionary<string, T> LoadAll(IEnumerable<string>? sections = null)
        {
            var result = new Dictionary<string, T>();
            var filter = new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name);
            if(sections != null)
            {
                filter.And(x => x.Key).InList(sections.AsArray());
            }
            var data = Provider.Query(
                filter,
                Columns.None<GlobalSettings>().Add(x => x.Key, x => x.Contents),
                new SortOrder<GlobalSettings>(x => x.Key));
            foreach (var row in data.Rows)
            {
                var tObj = Serialization.Deserialize<T>(row.Get<GlobalSettings, string>(c => c.Contents));
                if(tObj is BaseObject obj)
                {
                    obj.CommitChanges();
                }
                result[row.Get<GlobalSettings, string>(c => c.Key)] = tObj;
            }
            return result;
        }

        void IGlobalConfiguration.Save(object obj) => Save((T)obj);
        
        public override void Save(T obj)
        {
            ConfigurationCache.Add(ConfigurationCacheType.Global, Section, obj);

            var setting = GetSettings();

            setting.Contents = Serialization.Serialize(obj);

            Provider.Save(setting);
        }

        public override void SaveAll(Dictionary<string, T> objs, bool reset = false)
        {
            foreach (var (section, obj) in objs)
            {
                ConfigurationCache.Add(ConfigurationCacheType.Global, section, obj);
            }

            var data = reset 
                ? new Dictionary<string, GlobalSettings>()
                : Provider.Load(new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
                    .And(x => x.Key).InList(objs.Keys.ToArray())).ToDictionary(x => x.Key, x => x);

            var settings = new List<GlobalSettings>(objs.Count);
            foreach(var (section, obj) in objs)
            {
                var contents = Serialization.Serialize(obj);
                if (!data.TryGetValue(section, out var setting))
                {
                    setting = NewSettings(section);
                }
                setting.Contents = contents;
                settings.Add(setting);
            }
            Provider.Save(settings);
        }

        public override void Delete(Action<Exception?>? callback = null)
        {
            ConfigurationCache.Clear<T>(ConfigurationCacheType.Global, Section);

            var result = Provider.Load(new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name).And(x => x.Key).IsEqualTo(Section))
                .FirstOrDefault();

            if (result != null)
            {
                Provider.Delete(result, callback);
            }
        }

        public override string[] Sections()
        {
            var result = Provider.Query(
                new Filter<GlobalSettings>(x => x.Section).IsEqualTo(typeof(T).Name),
                Columns.None<GlobalSettings>().Add(x => x.Key),
                new SortOrder<GlobalSettings>(x => x.Key)
            ).Rows.Select(r => r.Get<GlobalSettings, string>(c => c.Key)).Distinct().ToArray();
            return result;
        }
    }
}