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

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

    [UserTracking(false)]
    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()
    {
        public UserConfiguration(string section = "") : base(section)
        {
        }

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

        private UserSettings GetSettings()
        {
            UserSettings result = null;
            if (ClientFactory.ClientType != null)
            {
                var client = new Client<UserSettings>();

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


                result = client.Load(filter).FirstOrDefault(x => string.Equals(x.UserID, ClientFactory.UserID));
            }
            return result ?? NewSettings();
        }

        private string GetKey(string? section = null)
        {
            return string.Format("{0}.{1}", section ?? Section, ClientFactory.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);

            ConfigurationCache.Add(ConfigurationCacheType.User, GetKey(), result);

            return result;
        }

        public override Dictionary<string, T> LoadAll()
        {
            var result = new Dictionary<string, T>();
            var data = new Client<UserSettings>().Query(
                new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name).And(x => x.UserID).IsEqualTo(ClientFactory.UserID),
                new Columns<UserSettings>(x => x.Key, x => x.Contents),
                new SortOrder<UserSettings>(x => x.Key)
            );
            foreach (var row in data.Rows)
                result[row.Get<UserSettings, string>(c => c.Key)] = Serialization.Deserialize<T>(row.Get<UserSettings, string>(c => c.Contents));
            return result;
        }

        public override void Save(T obj)
        {
            ConfigurationCache.Add(ConfigurationCacheType.User, GetKey(), obj);

            var setting = GetSettings();

            setting.Contents = Serialization.Serialize(obj);

            new Client<UserSettings>().Save(setting, "", (o, e) => { });
        }

        public override void SaveAll(Dictionary<string, T> objs)
        {
            foreach (var (section, obj) in objs)
            {
                ConfigurationCache.Add(ConfigurationCacheType.User, GetKey(section), obj);
            }

            var client = new Client<UserSettings>();
            var data = client.Load(
                new Filter<UserSettings>(x => x.Section).IsEqualTo(typeof(T).Name)
                    .And(x => x.Key).InList(objs.Keys.ToArray())
                    .And(x => x.UserID).IsEqualTo(ClientFactory.UserID))
                .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);
            }
            client.Save(settings, "", (o, e) => { });
        }

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

            var client = new Client<UserSettings>();

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

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

            if (result != null)
            {
                if(callback != null)
                {
                    client.Delete(result, "", (o, e) => { callback(e); });
                }
                else
                {
                    client.Delete(result, "");
                }
            }
        }

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