using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace InABox.Core
{
    public class StandardProperty : IProperty
    {
        public Type _class;

        private Func<object, object> _getter;
        private Func<object, object> _nullSafeGetter;

        private string _name = "";

        private Action<object, object?> _setter;

        private bool _checkedExpressions;

        public string _type = "";
        public Type _propertyType = typeof(object);

        public StandardProperty()
        {
            Editor = new NullEditor();
            HasEditor = false;
        }

        public string Class
        {
            get => _class.EntityName();
            set
            {
                _class = CoreUtils.GetEntity(value);
                ClearExpressions();
            }
        }

        public Type? ClassType
        {
            get => _class;
            set
            {
                _class = value ?? typeof(object);
            }
        }

        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                ClearExpressions();
            }
        }

        public string Type
        {
            get => _type;
            set
            {
                Type propType;
                try
                {
                    propType = CoreUtils.GetEntityOrNull(_type) ?? typeof(object);
                }
                catch
                {
                    propType = typeof(object);
                }
                PropertyType = propType; // Invoke other setter to ensure consistency of functionality
            }
        }

        public Type PropertyType
        {
            get => _propertyType;
            set
            {
                _propertyType = value;
                _type = value.EntityName();
                IsEntityLink = _propertyType.HasInterface(typeof(IEntityLink));
                IsEnclosedEntity = _propertyType.HasInterface(typeof(IEnclosedEntity));
                IsParent = IsEnclosedEntity || IsEntityLink;
            }
        }

        public PropertyInfo Property { get; set; }

        //[ComboLookupEditor(typeof(PropertyEditorLookups))]
        public BaseEditor Editor { get; set; }

        public bool HasEditor { get; set; }

        public string Caption { get; set; }

        public string Comment { get; set; }


        private decimal _propertySequence;

        private long _sequence;
        public long Sequence
        {
            get => _sequence;
            set
            {
                _sequence = value;
                _propertySequence = CalculatePropertySequence();
            }
        }

        [NullEditor]
        public string Page { get; set; }

        public bool Required { get; set; }

        public LoggablePropertyAttribute? Loggable { get; set; }

        private IProperty? _parent;
        public IProperty? Parent
        {
            get => _parent;
            set
            {
                _parent = value;
                _propertySequence = CalculatePropertySequence();
            }
        }

        public bool IsEntityLink { get; set; }

        public bool IsEnclosedEntity { get; set; }

        public bool IsParent { get; set; }

        private bool? _calculated;
        public bool IsCalculated
        {
            get
            {
                if (!_calculated.HasValue)
                {
                    _calculated = Property.GetCustomAttribute<AggregateAttribute>() != null
                        || Property.GetCustomAttribute<ComplexFormulaAttribute>() != null
                        || Property.GetCustomAttribute<FormulaAttribute>() != null
                        || Property.GetCustomAttribute<ConditionAttribute>() != null
                        || Property.GetCustomAttribute<ChildEntityAttribute>() != null
                        || this.GetParent(x => x.IsCalculated) != null;
                }
                return _calculated.Value;
            }
        }

        private bool IsPropertyPersistable(StandardProperty? prop)
        {
            if (prop == null)
                return true;
            if (prop.Property.GetCustomAttribute<DoNotPersist>() != null)
                return false;
            return IsPropertyPersistable(prop.Parent as StandardProperty);
        }
        private bool IsPropertySerializable(StandardProperty? prop)
        {
            if (prop == null)
                return true;
            if (prop.Property.GetCustomAttribute<DoNotSerialize>() != null)
                return false;
            return IsPropertySerializable(prop.Parent as StandardProperty);
        }

        public bool IsPersistable => IsPropertyPersistable(this);

        public bool IsSerializable => IsPropertySerializable(this);
        
        private bool? _dbColumn;
        public bool IsDBColumn
        {
            get
            {
                if (!_dbColumn.HasValue)
                {
                    _dbColumn = !IsCalculated
                        && IsPersistable
                        && Property.CanWrite
                        && (!this.HasParentEntityLink() || (Parent?.HasParentEntityLink() != true && Property.Name == "ID"));
                }
                return _dbColumn.Value;
            }
        }

        public TAttribute? GetAttribute<TAttribute>() where TAttribute : Attribute
        {
            return Property.GetCustomAttribute<TAttribute>();
        }

        public Expression Expression()
        {
            return CoreUtils.CreateMemberExpression(_class, Name);
        }

        public Func<object, object> Getter()
        {
            if (_getter is null) CheckExpressions();

            return _getter;
        }

        Func<object, object> IProperty.NullSafeGetter()
        {
            _nullSafeGetter ??= Expressions.Getter(_class, _name, propagateNulls: true);
            return _nullSafeGetter;
        }

        public Action<object, object?> Setter()
        {
            if (_setter is null) CheckExpressions();

            return _setter;
        }

        public decimal PropertySequence() => _propertySequence;

        private decimal CalculatePropertySequence()
        {
            var sequence = 0.0M;
            IProperty? property = this;
            while(property != null)
            {
                sequence = property.Sequence + sequence / 1000.0M;

                property = property.Parent;
            }
            return sequence;
        }

        public override string ToString()
        {
            return string.Format("{0}.{1}", Class, Name);
        }

        private void ClearExpressions()
        {
            _checkedExpressions = false;
            _getter = null;
            _setter = null;
        }

        private void CheckExpressions()
        {
            if(_checkedExpressions) return;

            if (_class == null)
                return;
            if (string.IsNullOrEmpty(_name))
                return;

            try
            {
                _getter = Expressions.Getter(_class, _name);
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            }

            try
            {
                _setter = Expressions.Setter(_class, _name);
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            }

            _checkedExpressions = true;
        }
    }
}