using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.WPF;

namespace InABox.DynamicGrid
{
    public interface IDynamicManyToManyGrid<TManyToMany, TThis> : IDynamicEditorPage
    {
    }

    public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany>, IDynamicEditorPage, IDynamicManyToManyGrid<TManyToMany, TThis>
        where TThis : Entity, new()
        where TManyToMany : Entity, IPersistent, IRemotable, new()
    {
        //private Guid ID = Guid.Empty;
        protected TThis Item;
        private TManyToMany[] MasterList = { };
        protected PropertyInfo otherproperty;

        protected PropertyInfo thisproperty;
        protected List<TManyToMany> WorkingList = new();

        public DynamicManyToManyGrid()
        {
            MultiSelect = true;
            thisproperty = CoreUtils.GetManyToManyThisProperty(typeof(TManyToMany), typeof(TThis));
            otherproperty = CoreUtils.GetManyToManyOtherProperty(typeof(TManyToMany), typeof(TThis));

            Options.BeginUpdate();

            Options.Add(DynamicGridOption.RecordCount)
                .Add(DynamicGridOption.SelectColumns)
                .Add(DynamicGridOption.MultiSelect);

            if (Security.CanEdit<TManyToMany>())
                Options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
            if (Security.CanDelete<TManyToMany>())
                Options.Add(DynamicGridOption.DeleteRows);
            if (Security.CanImport<TManyToMany>())
                Options.Add(DynamicGridOption.ImportData);
            if (Security.CanExport<TManyToMany>())
                Options.Add(DynamicGridOption.ExportData);
            if (Security.CanMerge<TManyToMany>())
                Options.Add(DynamicGridOption.MultiSelect);

            Options.EndUpdate();

            HiddenColumns.Add(x => x.ID);
            HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TManyToMany>(otherproperty.Name + ".ID"));
        }

        public bool MultiSelect { get; set; }

        public DynamicEditorGrid EditorGrid { get; set; }

        public string Caption()
        {
            //var m2m = typeof(TManyToMany).GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Contains(typeof(TThis)));
            //Type other = m2m.GenericTypeArguments.FirstOrDefault(x => x != typeof(TThis));
            //var serv = System.Data.Entity PluralizationService.CreateService(new System.Globalization.CultureInfo("en-us"));
            //var plural = serv.Pluralize(source);
            //return MvcHtmlString.Create(plural);

            var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(OtherType().Name);
            return result;
        }

        public virtual int Order()
        {
            return int.MinValue;
        }

        public bool Ready { get; set; }

        public void Load(object item, Func<Type, CoreTable>? PageDataHandler)
        {
            Item = (TThis)item;

            CoreTable? data = null;
            if (PageDataHandler != null)
                data = PageDataHandler.Invoke(typeof(TManyToMany));
            if (data != null)
            {
                RefreshData(data);
            }
            else
            {
                if (Item.ID == Guid.Empty)
                {
                    data = new CoreTable();
                    data.LoadColumns(typeof(TManyToMany));
                    RefreshData(data);
                }
                else
                {
                    var exp = CoreUtils.GetPropertyExpression<TManyToMany>(thisproperty.Name + ".ID");
                    var filter = new Filter<TManyToMany>(exp).IsEqualTo(Item.ID).And(exp).IsNotEqualTo(Guid.Empty);
                    var sort = LookupFactory.DefineSort<TManyToMany>();
                    new Client<TManyToMany>().Query(filter, null, sort, (o, e) => {
                        if(o != null)
                        {
                            Dispatcher.Invoke(() => RefreshData(o));
                        }
                        else if(e != null)
                        {
                            Logger.Send(LogType.Information, ClientFactory.UserID, $"Unknown Error: {CoreUtils.FormatException(e)}");
                            MessageBox.Show("An error occurred while loading data.");
                        }
                    });
                }
            }
        }

        public void BeforeSave(object item)
        {
            // Don't need to do anything here
        }

        public void AfterSave(object item)
        {
            // First remove any deleted files
            foreach (var map in MasterList)
                if (!WorkingList.Contains(map))
                    new Client<TManyToMany>().Delete(map, typeof(TManyToMany).Name + " Deleted by User");

            foreach (var map in WorkingList)
            {
                var prop = (IEntityLink)thisproperty.GetValue(map);
                if (prop.ID != Item.ID)
                    prop.ID = Item.ID;
            }

            if (WorkingList.Any(x => x.IsChanged()))
                new Client<TManyToMany>().Save(WorkingList.Where(x => x.IsChanged()), "Updated by User");
        }

        public Size MinimumSize()
        {
            return new Size(400, 400);
        }

        private Type OtherType() =>
            CoreUtils.GetManyToManyOtherType(typeof(TManyToMany), typeof(TThis));

        protected override DynamicGridColumns LoadColumns()
        {
            var tag = typeof(TManyToMany).Name + "." + typeof(TThis).Name;
            var global = Task.Run(() => new GlobalConfiguration<DynamicGridColumns>(tag).Load());
            var user = Task.Run(() => new UserConfiguration<DynamicGridColumns>(tag).Load());
            Task.WaitAll(global, user);
            var columns = user.Result.Any() ? user.Result : global.Result;
            if (columns.Count == 0)
                columns.AddRange(base.LoadColumns().Where(x => !x.ColumnName.StartsWith(thisproperty.Name)));
            return columns;
        }

        protected override void SaveColumns(DynamicGridColumns columns)
        {
            var tag = typeof(TManyToMany).Name + "." + typeof(TThis).Name;
            new UserConfiguration<DynamicGridColumns>(tag).Save(columns);
        }

        protected virtual Guid[] CurrentGuids()
        {
            var result = new List<Guid>();
            foreach (var item in WorkingList)
            {
                var prop = (IEntityLink)otherproperty.GetValue(item);
                result.Add(prop.ID);
            }

            return result.ToArray();
        }


        protected virtual object GetFilter()
        {
            var result = LookupFactory.DefineFilter(OtherType(), typeof(TThis), new[] { (TThis)Item }) as IFilter;

            var filtertype = typeof(Filter<>).MakeGenericType(OtherType());

            var filtermethod = filtertype.GetMethods(BindingFlags.Public | BindingFlags.Static).Where(x =>
                x.Name.Equals("List") && x.GetParameters().Last().ParameterType.IsAssignableFrom(typeof(IEnumerable<Guid>))).First();
            var filterexpression = CoreUtils.GetPropertyExpression(OtherType(), "ID");
            var filtervalues = CurrentGuids();
            var filter = filtermethod.Invoke(null, new object[] { filterexpression, ListOperator.Excludes, filtervalues }) as IFilter;

            if (filter != null)
            {
                if (result != null)
                {
                    filter.And(result);
                }

                return filter;
            }

            if (result != null) return result;

            return null;
        }

        protected override void DoAdd()
        {
            if (MultiSelect)
            {
                var filter = GetFilter();

                var dlgtype = typeof(MultiSelectDialog<>).MakeGenericType(OtherType());
                var dlg = Activator.CreateInstance(dlgtype, filter, null, true) as IMultiSelectDialog;
                if (dlg.ShowDialog())
                {
                    var guids = CurrentGuids();
                    var items = dlgtype.GetMethod("Items").Invoke(dlg, new object[] { null }) as IEnumerable;
                    foreach (var item in items)
                    {
                        var entity = item as Entity;
                        if (!guids.Contains(entity.ID))
                        {
                            var newitem = CreateItem();
                            var prop = (IEntityLink)otherproperty.GetValue(newitem);
                            prop.ID = entity.ID;
                            prop.Synchronise(entity);
                            SaveItem(newitem);
                        }
                    }

                    Refresh(false, true);
                }
            }
            else
            {
                base.DoAdd();
            }
        }

        protected override TManyToMany CreateItem()
        {
            var result = new TManyToMany();
            if (Item != null)
            {
                var prop = (IEntityLink)thisproperty.GetValue(result);
                prop.ID = Item.ID;
                prop.Synchronise(Item);
            }

            return result;
        }

        protected override TManyToMany LoadItem(CoreRow row)
        {
            return WorkingList[row.Index];
        }

        protected override void SaveItem(TManyToMany item)
        {
            if (!WorkingList.Contains(item))
                WorkingList.Add(item);
        }

        protected override void DeleteItems(params CoreRow[] rows)
        {
            foreach (var row in rows)
            {
                var id = row.Get<TManyToMany, Guid>(c => c.ID);
                var item = WorkingList.FirstOrDefault(x => x.ID.Equals(id));
                if (item != null)
                    WorkingList.Remove(item);
            }
        }

        private void RefreshData(CoreTable data)
        {
            MasterList = data.Rows.Select(x => x.ToObject<TManyToMany>()).ToArray();
            WorkingList = MasterList.ToList();
            Refresh(true, true);
            Ready = true;
        }

        protected override void Reload(Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort,
            Action<CoreTable?, Exception?> action)
        {
            var results = new CoreTable();
            results.LoadColumns(typeof(TManyToMany));

            if (sort != null)
            {
                var exp = IQueryableExtensions.ToLambda<TManyToMany>(sort.Expression);
                var sorted = sort.Direction == SortDirection.Ascending
                    ? WorkingList.AsQueryable().OrderBy(exp)
                    : WorkingList.AsQueryable().OrderByDescending(exp);
                foreach (var then in sort.Thens)
                {
                    var thexp = IQueryableExtensions.ToLambda<TManyToMany>(then.Expression);
                    sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
                }

                WorkingList = sorted.ToList();
            }
            results.LoadRows(WorkingList);

            //results.LoadRows(WorkingList);
            action.Invoke(results, null);
        }

        protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
        {
            var type = CoreUtils.GetProperty(typeof(TManyToMany), column.ColumnName).DeclaringType;
            if (type.GetInterfaces().Contains(typeof(IEntityLink)) && type.ContainsInheritedGenericType(typeof(TThis)))
                return new NullEditor();
            return base.GetEditor(item, column);
        }

        protected override Document LoadDocument(Guid id)
        {
            return new Client<Document>().Load(new Filter<Document>(x => x.ID).IsEqualTo(id)).FirstOrDefault();
        }

        protected override Document FindDocument(string filename)
        {
            return new Client<Document>().Load(new Filter<Document>(x => x.FileName).IsEqualTo(filename)).FirstOrDefault();
        }

        protected override void SaveDocument(Document document)
        {
            new Client<Document>().Save(document, "Updated by Editor");
        }

        public override void LoadEditorButtons(TManyToMany item, DynamicEditorButtons buttons)
        {
            base.LoadEditorButtons(item, buttons);
            if (ClientFactory.IsSupported<AuditTrail>())
                buttons.Add("Audit Trail", Properties.Resources.view.AsBitmapImage(), item, AuditTrailClick);
        }

        private void AuditTrailClick(object sender, object item)
        {
            var entity = (TManyToMany)item;
            var window = new AuditWindow(entity.ID);
            window.ShowDialog();
        }

        public override DynamicEditorPages LoadEditorPages(TManyToMany item)
        {
            return item.ID != Guid.Empty ? base.LoadEditorPages(item) : new DynamicEditorPages();
        }

        protected override bool BeforePaste(IEnumerable<TManyToMany> items, ClipAction action)
        {
            if (action == ClipAction.Copy)
            {
                foreach (var item in items)
                {
                    item.ID = Guid.Empty;
                }
            }
            return base.BeforePaste(items, action);
        }
    }
}