using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using InABox.Clients;

namespace InABox.Core
{
    public class DigitalFormDataModel<TEntity, TEntityLink, TInstance> : IDigitalFormDataModel
        where TEntity : Entity, IRemotable, IPersistent, new()
        where TEntityLink : EntityLink<TEntity>
        where TInstance : Entity, IRemotable, IPersistent, IDigitalFormInstance<TEntityLink>, new()
    {
        private readonly bool bRequiresLoad = true;
        public DigitalFormDataModel(Guid entityid, Guid instanceid)
        {
            Entity = new TEntity();
            Entity.ID = entityid;
            Entity.CommitChanges();
            Instance = new TInstance();
            Instance.ID = instanceid;
            Variables = new DigitalFormVariable[] { };
            bRequiresLoad = true;
        }

        public DigitalFormDataModel(TEntity entity, TInstance instance)
        {
            Entity = entity;
            Instance = instance;
            Variables = new DigitalFormVariable[] { };
            bRequiresLoad = false;
        }

        public Entity Entity { get; set; }

        public object? GetEntityValue(string name)
        {
            try
            {
                return CoreUtils.GetPropertyValue(Entity, name);
            }
            catch
            {
                return null;
            }
        }

        public void SetEntityValue(string name, object? value)
        {
            try
            {
                CoreUtils.SetPropertyValue(Entity, name, value);
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            }
        }

        IDigitalFormInstance IDigitalFormDataModel.Instance
        {
            get => Instance;
            set
            {
                Instance = (TInstance)value;
            }
        }

        public TInstance Instance { get; set; }
        
        public DigitalFormVariable[] Variables { get; set; }

        public event DigitalFormUpdateHandler OnModelSaved;

        public event DigitalFormBeforeUpdateHandler BeforeModelSaved;

        public void Load(Action<IDigitalFormDataModel>? callback = null)
        {
            if (!bRequiresLoad)
            {
                callback?.Invoke(this);
                return;
            }

            var client = new MultiQuery();

            DoAddQueries(client);

            if (callback == null)
            {
                if (client.Count > 0)
                {
                    client.Query();
                    DoParseQueries(client);
                }
            }
            else
            {
                if (client.Count > 0)
                    client.Query(c =>
                    {
                        DoParseQueries(client);
                        callback.Invoke(this);
                    });
                else
                    callback.Invoke(this);
            }
        }

        public void Update(Action<IDigitalFormDataModel>? callback = null)
        {
            //this never get used and causes a crash in the app for Job ITP forms - maybe time for a rebuild?
            //if (!bRequiresLoad)
            //{
            //    callback.Invoke(this);
            //    return;
            //}

            if (callback == null)
                DoUpdate();
            else
                Task.Run(() =>
                {
                    DoUpdate();
                    callback.Invoke(this);
                });
        }

        public virtual void AddQueries(MultiQuery client)
        {
        }

        private void DoAddQueries(MultiQuery client)
        {
            if (Entity.ID != Guid.Empty)
                client.Add(
                    new QueryDef<TEntity>(
                        new Filter<TEntity>(x => x.ID).IsEqualTo(Entity.ID),
                        Columns.None<TEntity>().Add(x => x.ID),
                        null
                    ),
                    typeof(TEntity)
                );

            if (Instance.ID != Guid.Empty)
                client.Add(
                    new QueryDef<TInstance>(
                        new Filter<TInstance>(x => x.ID).IsEqualTo(Instance.ID),
                        Columns.None<TInstance>().Add
                        (
                            x => x.ID, 
                            x => x.Number,
                            x => x.FormData, 
                            x => x.Form.ID,
                            x => x.Form.Description,
                            x => x.Form.DescriptionExpression,
                            x => x.FormCompleted,
                            x => x.FormCompletedBy.ID,
                            x => x.Created,
                            x => x.FormOpen,
                            x => x.BlobData
                            
                            ),
                        null
                    ),
                    typeof(TInstance)
                );

            AddQueries(client);
        }

        public virtual void ParseQueries(MultiQuery client)
        {
        }

        private void DoParseQueries(MultiQuery client)
        {
            if (client.Contains(typeof(TEntity)))
            {
                var table = client.Get(typeof(TEntity));
                if (table.Rows.Any())
                    Entity = table.Rows.First().ToObject<TEntity>();
            }

            if (client.Contains(typeof(TInstance)))
            {
                var table = client.Get(typeof(TInstance));
                if (table.Rows.Any())
                    Instance = table.Rows.First().ToObject<TInstance>();
            }

            ParseQueries(client);
        }

        protected virtual void AddPrimaryUpdates(MultiSave client)
        {
        }

        protected virtual void AddSecondaryUpdates(MultiSave client)
        {
        }

        public void DuplicateInstance()
        {
            var formid = Instance.Form.ID;
            Instance = new TInstance();
            Instance.Parent.ID = Entity.ID;
            Instance.Form.ID = formid;
        }

        public string? EvaluateExpression(string expression)
        {
            if (string.IsNullOrWhiteSpace(expression))
                return null;
            
            Dictionary<string, object?> variables = new Dictionary<string, object?>();
                
            var formData = DigitalForm.DeserializeFormData(Instance);
            if(formData != null)
            {
                foreach (var item in formData.Items())
                    variables[$"Data.{item.Key}"] = item.Value;
            }
                
            foreach (var property in DatabaseSchema.Properties(Instance.GetType()).Where(x => !x.Name.StartsWith("Parent.")))
                variables[$"Form.{property.Name}"] = property.Getter()(Instance);
                
            foreach (var property in DatabaseSchema.Properties(Entity.GetType()))
                variables[$"{typeof(TEntity).Name}.{property.Name}"] = property.Getter()(Entity);

            var exp = new CoreExpression(expression);
            return exp.Evaluate(variables)?.ToString();
        }

        private void DoUpdate()
        {

            var result = EvaluateExpression(Instance.Form.DescriptionExpression);
            Instance.Description = !String.IsNullOrWhiteSpace(result) 
                ? result 
                : Instance.Form.Description;
            
            BeforeModelSaved?.Invoke(this);

            var entityaudittrail = "";
            var instanceaudittrail = "";

            var parents = new MultiSave();
            if (Entity.IsChanged())
            {
                parents.Add(typeof(TEntity), Entity);
                entityaudittrail = "Updated from App - Digital Forms Module";
            }
            else if (Entity.ID == Guid.Empty)
            {
                parents.Add(typeof(TEntity), Entity);
                entityaudittrail = "Created from App - Digital Forms Module";
            }

            AddPrimaryUpdates(parents);

            parents.Save(null, entityaudittrail);

            ((IDigitalFormInstance<TEntityLink>)Instance).Parent.ID = Entity.ID; //be careful of bug here in future due to cast            

            var children = new MultiSave();
            if (Instance.IsChanged())
            {
                children.Add(typeof(TInstance), (TInstance)Instance);
                instanceaudittrail = "Updated from App - Digital Forms Module";
            }
            else if (Entity.ID == Guid.Empty)
            {
                children.Add(typeof(TInstance), (TInstance)Instance);
                instanceaudittrail = "Created from App - Digital Forms Module";
            }

            AddSecondaryUpdates(children);

            children.Save(null, instanceaudittrail);

            OnModelSaved?.Invoke(this);
        }
    }
}