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

namespace InABox.Core
{
    public interface IEntityLink
    {
        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();

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

        public EntityLink(Func<BaseObject>? entity) 
        {
            _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));
                                }
                            }
                        }
                    }
                }
            }

            var le = LinkedEntity();
            if (le != null)
                foreach (var link in LinkedProperties.Find(le,this))
                {
                    link.Update(this, le);
                    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)
        {
            var le = LinkedEntity();
            if (IsObserving() && le != null)
            {
                var link = LinkedProperties.Find(le, this).FirstOrDefault(x => x.Source.Equals(name));
                if (link != null)
                    link.Update(this, le);
            }
        }

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