using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Core;
using InABox.Wpf;
using InABox.Core.Reports;
using Syncfusion.Data.Extensions;

namespace InABox.DynamicGrid
{
    [Caption("Set Default Column Selections")]
    public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
    {
    }

    [LibraryInitializer]
    public static class DynamicGridUtils
    {
        private static IEnumerable<Type>? _allm2mtypes;
        private static IEnumerable<Type>? _allm2mpages;

        private static IEnumerable<Type>? _allo2mtypes;
        private static IEnumerable<Type>? _allo2mpages;

        private static IEnumerable<Type>? _allcepages;

        private static IEnumerable<Type>? _alleltypes;
        
        private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _onetomanypages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
        private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _manytomanytomanypages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
        private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _enclosedlistpages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
        private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _customeditorpages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();

        // HACK: These are really dumb
        public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
        public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }

        public static readonly MainResources Resources = new();

        public static void RegisterClasses()
        {
            // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
            // AssemblyName assemblyName = new AssemblyName(assyname);
            // AppDomain appDomain = Thread.GetDomain();
            //
            // String assyFile = String.Format("{0}.dll", assemblyName.Name);
            // String path = "";
            // if (Assembly.GetEntryAssembly() != null)
            // {
            //     path = Path.Combine(
            //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            //         Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
            //     );
            // }
            // else
            // {
            //     path = Path.Combine(
            //         Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            //         Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
            //     );
            // }
            //
            // if (!Directory.Exists(path))
            //     Directory.CreateDirectory(path);

            // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
            //
            // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
            //
            // if (_allm2mtypes == null)
            // {
            //     _allm2mtypes = CoreUtils.TypeList(
            //         AppDomain.CurrentDomain.GetAssemblies(),
            //         x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
            //     );
            // }
            //
            // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
            //
            // foreach (var map in maps)
            // {
            //     var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
            //     Type entity = intf.GenericTypeArguments.First();
            //     Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
            //     TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
            //     tbService.SetParent(basetype);
            //     Type final = tbService.CreateType();
            // }
            //
            // try
            // {
            //     assemblyBuilder.Save(assyFile);
            // }
            // catch (Exception e)
            // {
            //     Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
            // }
            
        }

        #region Pages

        public static IEnumerable<Type> GetManyToManyTypes(Type type)
        {
            _allm2mtypes ??= CoreUtils.TypeList(
                AppDomain.CurrentDomain.GetAssemblies(),
                x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
            );
            return _allm2mtypes.Where(x => x.GetInterfaces().Any(
                i => i.IsGenericType
                && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
                && i.GenericTypeArguments[0] == type)
            );
        }

        public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
        {
            if (!_manytomanytomanypages.ContainsKey(type))
            {

                var cache = new List<IDynamicEditorPage>();

                var maps = GetManyToManyTypes(type);

                foreach (var map in maps)
                {
                    if (ClientFactory.IsSupported(map))
                    {
                        _allm2mpages ??= CoreUtils.TypeList(
                                AppDomain.CurrentDomain.GetAssemblies(),
                                x => x.IsClass
                                     && !x.IsAbstract
                                     && !x.IsGenericType
                                     && x.GetInterfaces().Any(
                                         i => i.IsGenericType
                                              && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>)
                                     )
                            );

                        var subtypes = _allm2mpages.Where(
                            x => x.GetInterfaces().Any(i =>
                                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
                                i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
                        );

                        IDynamicEditorPage page;
                        if (subtypes.Any())
                        {
                            page = (Activator.CreateInstance(subtypes.First()) as IDynamicEditorPage)!;
                        }
                        else
                        {
                            var defType = typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type);
                            page = (Activator.CreateInstance(defType) as IDynamicEditorPage)!;
                        }

                        cache.Add(page);
                    }

                }
                _manytomanytomanypages[type] = cache;
            }
            pages.AddRange(_manytomanytomanypages[type]);
        }

        public static IEnumerable<Type> GetOneToManyTypes(Type type)
        {
            _allo2mtypes ??= CoreUtils.TypeList(
                AppDomain.CurrentDomain.GetAssemblies(),
                x => x.GetInterfaces().Any(i =>
                    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && x.GetCustomAttribute<ObsoleteAttribute>() == null)
            );
            return _allo2mtypes
                .Where(x => x.GetInterfaces().Any(i =>
                    i.IsGenericType
                    && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
                    && i.GenericTypeArguments.Contains(type)))
                .OrderBy(x => x.EntityName());
        }
        
        public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
        {
            if (!_onetomanypages.ContainsKey(type))
            {

                var cache = new List<IDynamicEditorPage>();

                var maps = GetOneToManyTypes(type);

                foreach (var map in maps)
                {
                    if (ClientFactory.IsSupported(map))
                    {

                        _allo2mpages ??= CoreUtils.TypeList(
                                AppDomain.CurrentDomain.GetAssemblies(),
                                x =>
                                    x.IsClass
                                    && !x.IsAbstract
                                    && !x.IsGenericType
                                    && x.GetInterfaces().Any(i =>
                                        i.IsGenericType
                                        && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
                                    )
                            );

                        var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
                                i => i.IsGenericType
                                     && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
                                     && i.GenericTypeArguments.First().Equals(type)
                                     && i.GenericTypeArguments.Last().Equals(map)
                            )
                        );

                        IDynamicEditorPage page;
                        if (subtypes.Any())
                        {
                            page = (Activator.CreateInstance(subtypes.First()) as IDynamicEditorPage)!;
                        }
                        else
                        {
                            var defType = typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map);
                            page = (Activator.CreateInstance(defType) as IDynamicEditorPage)!;
                        }

                        cache.Add(page);
                    }
                }

                _onetomanypages[type] = cache;
            }
            pages.AddRange(_onetomanypages[type]);
        }

        public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
        {
            if (!_customeditorpages.ContainsKey(type))
            {
                var cache = new List<IDynamicEditorPage>();
                _allcepages ??= CoreUtils.TypeList(
                        AppDomain.CurrentDomain.GetAssemblies(),
                        x => x.IsClass
                             && !x.IsAbstract
                             && !x.IsGenericType
                             && x.GetInterfaces().Any(i =>
                                 i.IsGenericType
                                 && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>)
                             )
                    );

                var pagetypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
                    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
                    i.GenericTypeArguments.First().Equals(type)));

                foreach (var pagetype in pagetypes)
                {
                    var page = (Activator.CreateInstance(pagetype) as IDynamicEditorPage)!;
                    cache.Add(page);
                }

                _customeditorpages[type] = cache;
            }
            pages.AddRange(_customeditorpages[type]);
        }

        public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
        {
            if (!_enclosedlistpages.ContainsKey(type))
            {
                var cache = new List<IDynamicEditorPage>();
                foreach (var property in type.GetProperties())
                {
                    if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
                    {
                        var curtype = property.PropertyType;
                        var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
                        while (gentype == null && curtype?.BaseType != null)
                        {
                            curtype = curtype.BaseType;
                            gentype = curtype?.GetGenericArguments().FirstOrDefault();
                        }

                        if (gentype != null)
                            if (gentype.IsSubclassOf(typeof(BaseObject)))
                            {
                                var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
                                if (editor == null || !(editor is NullEditor))
                                {
                                    _alleltypes ??= CoreUtils.TypeList(
                                            AppDomain.CurrentDomain.GetAssemblies(),
                                            x => x.IsClass
                                                 && !x.IsAbstract
                                                 && !x.IsGenericType
                                                 && x.GetInterfaces().Any(i =>
                                                     i.IsGenericType
                                                     && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
                                                 )
                                        );

                                    var subtypes = _alleltypes.Where(
                                        x => x.GetInterfaces().Any(
                                            i => i.IsGenericType
                                                 && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
                                                 && i.GenericTypeArguments.First().Equals(type)
                                                 && i.GenericTypeArguments.Last().Equals(gentype)
                                        )
                                    );

                                    IDynamicEditorPage page;
                                    if (subtypes.Any())
                                    {
                                        page = (Activator.CreateInstance(subtypes.First(), property) as IDynamicEditorPage)!;
                                        cache.Add(page);
                                    }
                                    else
                                    {
                                        subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => i.GenericTypeArguments.Last().Equals(gentype)));

                                        if (subtypes.Any())
                                        {
                                            var defType = subtypes.First().MakeGenericType(type);
                                            page = (Activator.CreateInstance(defType, property) as IDynamicEditorPage)!;
                                            cache.Add(page);
                                        }
                                        else
                                        {
                                            try
                                            {
                                                var defType = typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype);
                                                page = (Activator.CreateInstance(defType, property) as IDynamicEditorPage)!;
                                                cache.Add(page);
                                            }
                                            catch (Exception e)
                                            {
                                                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                                            }
                                        }
                                    }
                                }
                            }
                    }
                }

                _enclosedlistpages[type] = cache;
            }
            pages.AddRange(_enclosedlistpages[type]);
        }

        #endregion

        #region Editor Values

        

        public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object value)
        {
            Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
            var sw = new Stopwatch();

            var changes = new Dictionary<String, object?>(); 
            var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
            foreach (var item in items)
            {
                //Dictionary<String, object> previous = new Dictionary<string, object>();
                var previous = CoreUtils.GetValues(item, props);

                //if (item.OriginalValues != null)
                //{
                //    foreach (var key in item.OriginalValues.Keys)
                //        previous[key] = item.OriginalValues[key];
                //}

                var prop = DatabaseSchema.Property(item.GetType(), name);
                if (prop is CustomProperty)
                {
                    if (!item.HasOriginalValue(name))
                        item.SetOriginalValue(name, item.UserProperties[name]);
                    item.UserProperties[name] = value;
                }
                else
                {
                    if (prop != null)
                        try
                        {
                            var getter = prop.Getter();
                            var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);

                            item.OnPropertyChanged(name, oldvalue, value);

                            var setter = prop.Setter();
                            if (setter != null && value != null)
                                setter.Invoke(item, value);
                            else
                                CoreUtils.SetPropertyValue(item, name, value);
                        }
                        catch (Exception)
                        {
                            Logger.Send(LogType.Error, "",
                                string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
                        }
                }
                var current = CoreUtils.GetValues(item, props);
                CoreUtils.MergeChanges(previous, current, changes);

            }
            
            return changes;
        }

        public static void UpdateEditorValue(BaseObject[] items, string name, object value, Dictionary<string, object?> changes)
        {
            var results = UpdateEditorValue(items, name, value);
            foreach (var key in results.Keys)
                changes[key] = results[key];
        }

        #endregion

        #region Dynamic Grid Creation

        public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
        {
            var type = FindDynamicGrid(gridType, entityType);
            return (Activator.CreateInstance(type) as IDynamicGrid)
                ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
        }

        private static Dictionary<Type, Type[]> _dynamicGrids = new();

        public static Type FindDynamicGrid(Type gridType, Type entityType)
        {
            if(!_dynamicGrids.TryGetValue(gridType, out var grids))
            {
                grids = CoreUtils.TypeList(
                    AppDomain.CurrentDomain.GetAssemblies(),
                    myType =>
                        myType.IsClass
                        && !myType.IsAbstract
                        && !myType.IsGenericType
                        && myType.IsAssignableTo(typeof(IDynamicGrid))
                        && !myType.IsAssignableTo(typeof(ISpecificGrid))
                ).ToArray();
                _dynamicGrids[gridType] = grids;
            }
            grids = grids.Where(x=>x.IsSubclassOfRawGeneric(gridType)).ToArray();
            var entityGrids = grids.Where(x => x.ContainsInheritedGenericType(entityType)).ToList();

            var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
            if(defaults.Count > 0)
            {
                if(defaults.Count > 1)
                {
                    Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
                }
                return defaults.First();
            }

            return entityGrids.FirstOrDefault()
                ?? gridType.MakeGenericType(entityType);
        }

        public static Window CreateGridWindow(string title, BaseDynamicGrid dynamicGrid)
        {
            dynamicGrid.Margin = new Thickness(5);

            var window = new ThemableWindow { Title = title, Content = dynamicGrid };

            (dynamicGrid as IDynamicGrid)!.Refresh(true, true);

            return window;
        }
        public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
        {
            gridType ??= typeof(DynamicGrid<>);
            var grid = CreateDynamicGrid(gridType, entityType) as BaseDynamicGrid;
            return CreateGridWindow(title, grid!);
        }
        public static Window CreateGridWindow<TGrid, TEntity>(string title)
            where TEntity : BaseObject
            where TGrid : IDynamicGrid
        {
            return CreateGridWindow(title, typeof(TEntity), typeof(TGrid));
        }
        public static Window CreateGridWindow<TEntity>(string title)
            where TEntity : BaseObject
        {
            return CreateGridWindow(title, typeof(TEntity));
        }

        #endregion

        public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
            ItemsControl menu,
            Guid entityID,
            Func<TEntity> loadEntity,
            bool editOnAdd = false
            )
            where TEntityForm : EntityForm<TEntity, TEntityLink>, new()
            where TEntity : Entity
            where TEntityLink : IEntityLink<TEntity>, new()
        {
            var task = Task.Run(() =>
            {
                return new Client<TEntityForm>().Query(
                    new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
                    null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
            });

            var addForm = new MenuItem { Header = "Add Form" };
            addForm.Click += (o, e) =>
            {
                var entity = loadEntity();
                var filter = LookupFactory.DefineFilter<TEntity, DigitalForm>(new TEntity[] { entity })
                    ?? LookupFactory.DefineFilter<TEntityForm, DigitalForm>(Array.Empty<TEntityForm>());

                var select = new MultiSelectDialog<DigitalForm>(
                    filter,
                    LookupFactory.DefineColumns<DigitalForm>()
                        .Add(x => x.Description),
                    false);
                if(select.ShowDialog() == true)
                {
                    var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();

                    if(digitalForm is not null)
                    {
                        var form = new TEntityForm
                        {
                            Description = digitalForm.Description
                        };
                        form.Parent.ID = entityID;
                        form.Form.ID = digitalForm.ID;
                        if (editOnAdd)
                        {
                            if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel))
                            {
                                dataModel.Update(null);
                            }
                        }
                        else
                        {
                            new Client<TEntityForm>().Save(form, "Added by user");
                        }
                    }
                };
            };

            var manageForms = new MenuItem { Header = "Manage Forms..." };
            manageForms.Click += (o, e) =>
            {
                var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };

                var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());

                grid.Refresh(true, true);
                grid.Margin = new Thickness(5);

                window.Content = grid;

                window.ShowDialog();
            };

            menu.Items.Add(addForm);
            menu.Items.Add(new Separator());
            menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
            menu.Items.Add(new Separator());
            menu.Items.Add(manageForms);

            task.ContinueWith((task) =>
            {
                var entityForms = task.Result;
                menu.Items.Clear();
                menu.Items.Add(addForm);
                menu.Items.Add(new Separator());

                if (entityForms.Any())
                {
                    foreach (var entityForm in entityForms)
                    {
                        var description = entityForm.Description;
                        if (string.IsNullOrWhiteSpace(description))
                        {
                            description = entityForm.Form.Description;
                        }
                        var formItem = new MenuItem { Header = description };
                        formItem.Click += (o, e) =>
                        {
                            if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
                            {
                                dataModel.Update(null);
                            }
                        };

                        menu.Items.Add(formItem);
                    }
                }
                else
                {
                    menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
                }
                menu.Items.Add(new Separator());
                menu.Items.Add(manageForms);
            }, TaskScheduler.FromCurrentSynchronizationContext());
        }
    }
}