using Expressive;
using System;
using System.Dynamic;
using System.Linq;

namespace InABox.Core
{
    public interface IEntityLink : ISubObject
    {
        Guid ID { get; set; }
        
        Guid Deleted { get; set; }

        bool Synchronise(object entity);
        bool Clear();
        bool IsValid();
    }

    public interface IEntityLink<T> : IEntityLink
    {
    }

    public abstract class EntityLink<T> : BaseObject, IEntityLink<T> where T : BaseObject, new()
    {
        /*
        private Func<BaseObject>? _linkedentity;
        
        [DoNotSerialize]
        protected BaseObject? LinkedEntity() => _linkedentity?.Invoke();*/

        private BaseObject _linkedParent;

        private string _linkedPath;

        public void SetLinkedParent(BaseObject parent)
        {
            _linkedParent = parent;
        }

        public void SetLinkedPath(string path)
        {
            _linkedPath = path;
        }

        public BaseObject GetLinkedParent() => _linkedParent;

        public string GetLinkedPath() => _linkedPath;

        /*
        [Obsolete("Please supply linked Entity")]
        public EntityLink()
        {
        }

        public EntityLink(Func<BaseObject>? entity, string name) 
        {
            _linkedentity = entity;
        }*/

        [NullEditor]
        public abstract Guid ID { get; set; }
        
        [NullEditor]
        public Guid Deleted { get; set; }

        public virtual bool Synchronise(object Entity)
        {
            var result = false;
            var props = CoreUtils.PropertyList(GetType(), x => (!x.Name.Equals("ID") || Entity == null) && !x.DeclaringType.Equals(typeof(BaseObject)), false);
            foreach (var key in props.Keys)
            {
                if (CoreUtils.HasProperty(Entity.GetType(), key))
                {
                    var prop = CoreUtils.GetProperty(typeof(T), key);
                    if (prop != null && prop.PropertyType.Equals(props[key]))
                        
                    {
                        if (prop.PropertyType.GetInterfaces().Any(x => x == typeof(IEntityLink)))
                        {
                            var tgtlink = CoreUtils.GetPropertyValue(this, key) as IEntityLink;
                            var srclink = CoreUtils.GetPropertyValue(Entity, key) as IEntityLink;
                            tgtlink.ID = srclink.ID;
                            tgtlink.Synchronise(srclink);
                        }
                        else
                        {
                            var oldvalue = CoreUtils.GetPropertyValue(this, key);
                            var newvalue = CoreUtils.GetPropertyValue(Entity, key);
                            if ((oldvalue == null && newvalue != null) || (oldvalue != null && !oldvalue.Equals(newvalue)))
                            {
                                result = true;
                                try
                                {
                                    CoreUtils.SetPropertyValue(this, key, newvalue);
                                }
                                catch (Exception e)
                                {
                                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                                }
                            }
                        }
                    }
                }
            }

            foreach (var link in LinkedProperties.Find(this, out var parent))
            {
                link.Update(this, parent);
                result = true;
            }

            return result;
        }

        public bool Clear()
        {
            var result = false;
            var props = CoreUtils.PropertyList(GetType(), x => !x.Name.Equals("ID"), false);
            foreach (var key in props.Keys)
            {
                var prop = CoreUtils.GetProperty(typeof(T), key);
                if (prop != null && prop.PropertyType.Equals(props[key]))
                {
                    var oldvalue = CoreUtils.GetPropertyValue(this, key);
                    var newvalue = prop.PropertyType.Equals(typeof(string)) ? "" : Activator.CreateInstance(prop.PropertyType);
                    if ((oldvalue == null && newvalue != null) || (oldvalue != null && !oldvalue.Equals(newvalue)))
                    {
                        result = true;
                        CoreUtils.SetPropertyValue(this, key, newvalue);
                    }
                }
            }

            return result;
        }

        protected override void DoPropertyChanged(string name, object? before, object? after)
        {
            if (IsObserving())
            {
                if(LinkedProperties.Find(this, name, out var link, out var parent))
                {
                    link.Update(this, parent);
                }
            }
        }

        public bool IsValid() => !ID.Equals(Guid.Empty) && Deleted.Equals(Guid.Empty);
    }
}