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

namespace InABox.Clients
{
    public enum SerializerProtocol
    {
        Rest
    }

    public class QueryMultipleResults
    {
        private readonly Dictionary<string, CoreTable> Results;

        internal QueryMultipleResults(Dictionary<string, CoreTable> results)
        {
            Results = results;
        }

        public CoreTable this[string name] => Results[name];

        public CoreTable Get<T>() => Results[typeof(T).Name];

        public CoreTable Get(string name) => Results[name];
    }

    public abstract class Client
    {
        public abstract CoreTable Query(IFilter? filter = null, IColumns? columns = null, ISortOrder? sortOrder = null);
        public abstract void Save(Entity entity, string auditNote);

        private static IClient CheckClient()
        {
            using (new Profiler(true))
                return ClientFactory.CreateClient<User>();
        }

        public static Dictionary<string, CoreTable> QueryMultiple(Dictionary<string, IQueryDef> queries)
        {
            try
            {
                using var timer = new Profiler(false);
                var result = CheckClient().QueryMultiple(queries);
                timer.Log(result.Sum(x => x.Value.Rows.Count));
                return result;
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static QueryMultipleResults QueryMultiple(params IKeyedQueryDef[] queries)
        {
            try
            {
                using var timer = new Profiler(false);
                var result = CheckClient().QueryMultiple(queries.ToDictionary(x => x.Key, x => x as IQueryDef));
                timer.Log(result.Sum(x => x.Value.Rows.Count));
                return new QueryMultipleResults(result);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }
        public static QueryMultipleResults QueryMultiple(IEnumerable<IKeyedQueryDef> queries)
        {
            try
            {
                using var timer = new Profiler(false);
                var result = CheckClient().QueryMultiple(queries.ToDictionary(x => x.Key, x => x as IQueryDef));
                timer.Log(result.Sum(x => x.Value.Rows.Count));
                return new QueryMultipleResults(result);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static ValidationData Validate(Guid session)
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Validate(session);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static ValidationData Validate(string pin, Guid session = default)
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Validate(pin, session);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static ValidationData Validate(string userid, string password, Guid session = default)
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Validate(userid, password, session);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static bool Check2FA(string code, Guid? session = null)
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Check2FA(code, session);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static bool Ping()
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Ping();
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static DatabaseInfo Info()
        {
            try
            {
                using (new Profiler(true))
                    return CheckClient().Info();
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public static Client Create(Type TEntity) => 
            (Activator.CreateInstance(typeof(Client<>).MakeGenericType(TEntity)) as Client)!;

    }

    public class Client<TEntity> : Client, IDisposable where TEntity : Entity, IPersistent, IRemotable, new()
    {
        private IClient<TEntity> _client;

        public Client()
        {
            _client = ClientFactory.CreateClient<TEntity>();
        }

        public void Dispose()
        {
        }


        private void CheckSupported()
        {
            if (!ClientFactory.IsSupported<TEntity>())
                throw new NotSupportedException(string.Format("{0} is not supported in this context", typeof(TEntity).EntityName()));
        }

        private string FilterToString(Filter<TEntity> filter)
        {
            return filter != null ? filter.AsOData() : "";
        }

        private string OrderToString(SortOrder<TEntity> order)
        {
            return order != null ? order.AsOData() : "";
        }

        public CoreTable Query(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null)
        {
            try
            {
                using (var timer = new Profiler<TEntity>(false))
                {
                    CheckSupported();
                    var result = _client.Query(filter, columns, orderby);
                    timer.Log(result.Rows.Count);
                    return result;
                }
            }
            catch(RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }
        public override CoreTable Query(IFilter? filter, IColumns? columns, ISortOrder? sortOrder)
        {
            return Query(filter as Filter<TEntity>, columns as Columns<TEntity>, sortOrder as SortOrder<TEntity>);
        }

        public void Query(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort, Action<CoreTable?, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(false);
                CheckSupported();
                _client.Query(filter, columns, sort, (c, e) =>
                {
                    timer.Dispose(c != null ? c.Rows.Count : -1);
                    callback?.Invoke(c, e);
                });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public TEntity[] Load(Filter<TEntity>? filter = null, SortOrder<TEntity>? sort = null)
        {
            try
            {

                using (var timer = new Profiler<TEntity>(false))
                {
                    CheckSupported();
                    var result = _client.Load(filter, sort);
                    foreach (var entity in result)
                        entity.CommitChanges();
                    timer.Log(result.Length);
                    return result;
                }
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Load(Filter<TEntity> filter, SortOrder<TEntity> sort, Action<TEntity[]?, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(false);
                CheckSupported();
                _client.Load(filter, sort, (i, e) =>
                {
                    timer.Dispose(i != null ? i.Length : -1);
                    callback?.Invoke(i, e);
                });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public override void Save(Entity entity, string auditNote)
        {
            try
            {
                Save((entity as TEntity)!, auditNote);
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }
        public void Save(TEntity entity, string auditnote)
        {
            try
            {

                using (new Profiler<TEntity>(true))
                {
                    CheckSupported();
                    entity.LastUpdate = DateTime.Now;
                    entity.LastUpdateBy = ClientFactory.UserID;
                    _client.Save(entity, auditnote);
                    entity.CommitChanges();
                }
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Save(TEntity entity, string auditnote, Action<TEntity, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(false);
                CheckSupported();
                _client.Save(entity, auditnote, (i, c) =>
                {
                    timer.Dispose();
                    callback?.Invoke(i, c);
                });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Save(IEnumerable<TEntity> entities, string auditnote)
        {
            try
            {
                using var timer = new Profiler<TEntity>(false);
                CheckSupported();
                if (entities.Any())
                    _client.Save(entities, auditnote);
                timer.Log(entities.Count());
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Save(IEnumerable<TEntity> entities, string auditnote, Action<IEnumerable<TEntity>, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(false);
                CheckSupported();
                if (entities.Any())
                    _client.Save(entities, auditnote, (i, e) =>
                    {
                        timer.Dispose(i.Count());
                        callback?.Invoke(i, e);
                    });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }


        public void Delete(TEntity entity, string auditnote)
        {
            try
            {
                using (new Profiler<TEntity>(true))
                {
                    CheckSupported();
                    _client.Delete(entity, auditnote);
                }
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Delete(TEntity entity, string auditnote, Action<TEntity, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(true);
                CheckSupported();
                _client.Delete(entity, auditnote, (i, e) =>
                {
                    timer.Dispose();
                    callback?.Invoke(i, e);
                });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Delete(IList<TEntity> entities, string auditnote)
        {
            try
            {
                using var timer = new Profiler<TEntity>(false);
                CheckSupported();
                _client.Delete(entities, auditnote);
                timer.Log(entities.Count());
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public void Delete(IList<TEntity> entities, string auditnote, Action<IList<TEntity>, Exception?> callback)
        {
            try
            {
                var timer = new Profiler<TEntity>(false);
                CheckSupported();
                _client.Delete(entities, auditnote, (i, e) =>
                {
                    timer.Dispose(entities.Count());
                    callback?.Invoke(i, e);
                });
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public IEnumerable<string> SupportedTypes()
        {
            try
            {
                using (new Profiler(true))
                    return _client.SupportedTypes();
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }

        public new DatabaseInfo Info()
        {
            try
            {
                using (new Profiler(true))
                    return _client.Info();
            }
            catch (RequestException e)
            {
                ClientFactory.RaiseRequestError(e);
                throw;
            }
        }
    }
}