using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace InABox.Core
{
    public static class DatabaseSchema
    {
        // {className: {propertyName: property}}
        private static Dictionary<string, Dictionary<string, IProperty>> _Properties { get; }
            = new Dictionary<string, Dictionary<string, IProperty>>();

        /*private static BaseEditor GetPropertyEditor(Type type, PropertyInfo property, string propertyName)
        {
            BaseEditor editor = new NullEditor();

            var parts = propertyName.Split('.');
            var caption = "";
            var page = "";
            int? sequence = null;

            for (var i = 0; i < parts.Length; i++)
            {
                var column = string.Join(".", parts.Take(i + 1));
                var prop = CoreUtils.GetProperty(type, column);
                if (column.Equals(propertyName))
                {
                    editor = property.GetEditor() ?? new NullEditor();
                }
                else
                {
                    var pedit = prop.GetEditor();
                    if (pedit is NullEditor) return pedit;
                }

                editor = editor == null ? new NullEditor() : (editor.Clone() as BaseEditor)!;

                var capattr = prop.GetCustomAttribute<Caption>();
                var subcap = capattr != null ? capattr.Text : parts[i];
                var path = capattr == null || capattr.IncludePath;
                if (!string.IsNullOrWhiteSpace(subcap))
                    caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);

                if (string.IsNullOrWhiteSpace(page))
                {
                    var pageattr = prop.GetCustomAttribute<EditorSequence>();
                    if (pageattr != null)
                    {
                        page = pageattr.Page;
                        sequence = pageattr.Sequence;
                    }
                }
            }

            editor.Caption = caption;
            editor.Page = page;
            editor.EditorSequence = sequence ?? 999;
            return editor;
        }*/

        private static void RegisterProperties(Type master, Type type, string prefix, StandardProperty? parent)
        {
            try
            {
                var classname = master.EntityName();
                var properties = CoreUtils.PropertyList(
                    type,
                    x => !x.PropertyType.IsInterface &&
                        x.GetGetMethod()?.IsPublic == true &&
                        (x.DeclaringType.IsSubclassOf(typeof(BaseObject)) 
                        || x.DeclaringType.IsSubclassOf(typeof(BaseEditor)))
                ); //.OrderBy(x=>x.Name);
                var classProps = _Properties.GetValueOrDefault(classname);

                foreach (var prop in properties)
                {
                    var name = string.IsNullOrWhiteSpace(prefix) ? prop.Name : string.Format("{0}.{1}", prefix, prop.Name);
                    var p = classProps?.GetValueOrDefault(name);
                        //.ToArray().FirstOrDefault(x => x.Class == classname && x.Name == name);
                    if (p == null)
                    {
                        var isstatic = prop.GetAccessors(true)[0].IsStatic;
                        if (!isstatic)
                        {
                            BaseEditor? editor;
                            if (parent != null && parent.HasEditor && parent.Editor is NullEditor)
                            {
                                editor = parent.Editor;
                            }
                            else
                            {
                                editor = prop.GetEditor();
                            }

                            var captionAttr = prop.GetCustomAttribute<Caption>();
                            var subCaption = captionAttr != null ? captionAttr.Text : prop.Name;
                            var path = captionAttr == null || captionAttr.IncludePath; // If no caption attribute, we should always include the path
                            var caption = parent?.Caption ?? ""; // We default to the parent caption if subCaption doesn't exist
                            if (!string.IsNullOrWhiteSpace(subCaption))
                            {
                                if (!string.IsNullOrWhiteSpace(caption) && path)
                                {
                                    caption = $"{caption} {subCaption}";
                                }
                                else
                                {
                                    caption = subCaption;
                                }
                            }

                            // Once the parent page has been found, this property is cemented to that page - it cannot change page to its parent
                            // The same goes for sequence
                            var page = parent?.Page;
                            var sequence = parent?.Sequence;
                            if (string.IsNullOrWhiteSpace(page))
                            {
                                var sequenceAttribute = prop.GetCustomAttribute<EditorSequence>();
                                if (sequenceAttribute != null)
                                {
                                    page = sequenceAttribute.Page;
                                    sequence = sequenceAttribute.Sequence;
                                }
                            }
                            editor = editor?.Clone() as BaseEditor;
                            if (editor != null)
                            {
                                editor.Page = page;
                                editor.Caption = caption;
                                editor.EditorSequence = (int)(sequence ?? 999);
                            }

                            bool required = false;
                            if (parent == null || parent.Required)
                            {
                                required = prop.GetCustomAttribute<RequiredColumnAttribute>() != null;
                            }

                            LoggablePropertyAttribute? loggable = null;
                            if (parent == null || parent.Loggable != null)
                            {
                                loggable = prop.GetCustomAttribute<LoggablePropertyAttribute>();
                            }

                            var newProperty = new StandardProperty
                            {
                                _class = master,
                                //Class = classname,
                                Name = name,
                                PropertyType = prop.PropertyType,
                                Editor = editor ?? new NullEditor(),
                                HasEditor = editor != null,
                                Caption = caption,
                                Sequence = sequence ?? 999,
                                Page = page ?? "",
                                Required = required,
                                Loggable = loggable,
                                Parent = parent,
                                Property = prop
                            };

                            if (prop.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) ||
                                prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)) ||
                                prop.PropertyType.Equals(typeof(BaseEditor)) ||
                                prop.PropertyType.IsSubclassOf(typeof(BaseEditor)))
                            {
                                if (prop.PropertyType.GetInterfaces().Contains(typeof(IEnclosedEntity)))
                                {
                                    //RegisterProperty(newProperty);
                                }
                                RegisterProperties(master, prop.PropertyType, name, newProperty);
                            }
                            else
                            {
                                RegisterProperty(newProperty);
                            }
                        }
                    }
                }

                if (type.IsSubclassOf(typeof(BaseObject)))
                    //if ((type.BaseType != null) && (type.BaseType != typeof(BaseObject)))
                    RegisterProperties(master, type.BaseType, prefix, parent);
            }
            catch (Exception e)
            {
                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            }
        }

        private static void RegisterProperties(Type type)
        {
            RegisterProperties(type, type, "", null);
        }

        public static object? DefaultValue(Type type)
        {
            if (type.IsValueType)
                return Activator.CreateInstance(type);
            if (type.Equals(typeof(string)))
                return "";
            return null;
        }
        
        private static readonly object _updatelock =  new object();
        
        public static void RegisterProperty(IProperty entry)
        {
            lock (_updatelock)
            {
                if (!_Properties.ContainsKey(entry.Class))
                    _Properties[entry.Class] = new Dictionary<string, IProperty>();
                _Properties[entry.Class][entry.Name] = entry;
                //var dict = _Properties.GetOrAdd(entry.Class, className => new Dictionary<string, IProperty>());
                //dict.TryAdd(entry.Name, entry);
            }
        }

        public static void Load(CustomProperty[] customproperties)
        {
            foreach (var prop in customproperties)
                RegisterProperty(prop);
        }

        private static void CheckProperties(Type type)
        {
            var entityName = type.EntityName();
            if (type.IsSubclassOf(typeof(BaseObject)) && _Properties.GetValueOrDefault(entityName)?.Any(x => x.Value is StandardProperty) != true)
                RegisterProperties(type);
        }

        public static IEnumerable<IProperty> Properties(Type type)
        {
            CheckProperties(type);
            var entityName = type.EntityName();
            return _Properties.GetValueOrDefault(entityName)?.Select(x => x.Value) ?? Array.Empty<IProperty>();
        }
        
        public static IProperty? Property(Type type, string name)
        {
            CheckProperties(type);
            var entityName = type.EntityName();

            //IProperty prop = _Properties.ToArray().FirstOrDefault(x => (x.Class.Equals(type.EntityName())) && (x.Name.Equals(name)));
            var prop = _Properties.GetValueOrDefault(entityName)?.GetValueOrDefault(name);

            // Walk up the inheritance tree, see if an ancestor has this property
            if (prop == null && type.BaseType != null)
                prop = Property(type.BaseType, name);
            return prop;
        }

        public static void InitializeObject<TObject>(TObject entity) where TObject : BaseObject
        {
            entity.UserProperties.Clear();
            var props = Properties(entity.GetType()).Where(x => x is CustomProperty).ToArray();
            foreach (var field in props) entity.UserProperties[field.Name] = DefaultValue(field.PropertyType);
        }
    }
}