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

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

    public static class UserConfigurationExtensions
    {
        public static void SaveUser<T>(this T settings)
            where T : IUserConfigurationSettings, new()
        {
            new UserConfiguration<T>().Save(settings);
        }
    }

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

        public string Key { get; set; }

        public string UserID { get; set; }

        public string Contents { get; set; }
    }

    public class UserConfiguration<T> : Configuration<T> where T : IUserConfigurationSettings, new()
    {
        private IConfigurationProvider<UserSettings> Provider;

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

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

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

        private UserSettings GetSettings()
        {
            UserSettings result = null;
            var filter = new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
                .And(x => x.Key).IsEqualTo(Section)
                .And(x => x.UserID).IsEqualTo(Provider.UserID);


            result = Provider.Query(filter, Columns.All<UserSettings>())
                .ToObjects<UserSettings>().FirstOrDefault();

            return result ?? NewSettings();
        }

        private string GetKey(string? section = null)
        {
            return string.Format("{0}.{1}", section ?? Section, Provider.UserID);
        }

        public override T Load(bool useCache = true)
        {
            if (useCache)
            {
                var cached = ConfigurationCache.Check<T>(ConfigurationCacheType.User, GetKey());
                if (cached != null)
                    //Logger.Send(LogType.Information, "", "User 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.User, GetKey(), result);

            return result;
        }

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

        public override void Save(T obj)
        {
            ConfigurationCache.Add(ConfigurationCacheType.User, GetKey(), 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.User, GetKey(section), obj);
            }

            var data = reset 
                ? new Dictionary<string,UserSettings>() 
                : Provider.Query(
                    new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
                        .And(x => x.Key).InList(objs.Keys.ToArray())
                        .And(x => x.UserID).IsEqualTo(Provider.UserID),
                    Columns.All<UserSettings>())
                .ToObjects<UserSettings>()
                .ToDictionary(x => x.Key, x => x);

            var settings = new List<UserSettings>(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.User, GetKey());

            var filter = new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
                .And(x => x.Key).IsEqualTo(Section)
                .And(x => x.UserID).IsEqualTo(Provider.UserID);

            UserSettings? result;
            try
            {
                result = Provider.Load(filter).FirstOrDefault();
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, Provider.UserID, "Error in UserConfiguration.Delete(): " + CoreUtils.FormatException(e));
                result = null;
            }

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

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