using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.Core.Reports;
using InABox.WPF;

namespace InABox.DynamicGrid;

public interface IDynamicOneToManyGrid<TOne, TMany> : IDynamicEditorPage
{
    List<TMany> Items { get; }
    void LoadItems(TMany[] items);
}

public class DynamicOneToManyGrid<TOne, TMany> : DynamicGrid<TMany>,
    IDynamicEditorPage,
    IDynamicOneToManyGrid<TOne, TMany>,
    IDynamicMemoryEntityGrid<TMany>
    where TOne : Entity, new() where TMany : Entity, IPersistent, IRemotable, new()
{
    private TMany[] MasterList = Array.Empty<TMany>();
    private readonly PropertyInfo property;

    public HashSet<string>? LoadedColumns { get; set; }

    IEnumerable<TMany> IDynamicMemoryEntityGrid<TMany>.Items => Items;

    protected DynamicGridCustomColumnsComponent<TMany> ColumnsComponent;

    public virtual bool Visible => Security.CanView<TMany>();

    public DynamicOneToManyGrid()
    {
        Ready = false;
        Items = new List<TMany>();
        Criteria = new Filters<TMany>();

        property = CoreUtils.GetOneToManyProperty(typeof(TMany), typeof(TOne));

        AddHiddenColumn(property.Name + "." + nameof(IEntityLink.ID));
        foreach (var col in LookupFactory.RequiredColumns<TMany>())
            HiddenColumns.Add(col);

        ColumnsComponent = new DynamicGridCustomColumnsComponent<TMany>(this, GetTag());
    }

    protected override void DoReconfigure(DynamicGridOptions options)
    {
        base.DoReconfigure(options);

        options.RecordCount = true;
        options.SelectColumns = true;

        if (Security.CanEdit<TMany>() && !ReadOnly)
        {
            options.AddRows = true;
            options.EditRows = true;
        }
        if (Security.CanDelete<TMany>() && !ReadOnly)
            options.DeleteRows = true;
        if (Security.CanImport<TMany>() && !ReadOnly)
            options.ImportData = true;
        if (Security.CanExport<TMany>())
            options.ExportData = true;
        if (Security.CanMerge<TMany>())
            options.MultiSelect = true;
    }

    private static bool IsAutoEntity => typeof(TMany).HasAttribute<AutoEntity>();

    protected Filters<TMany> Criteria { get; } = new Filters<TMany>();

    public TOne Item { get; protected set; }

    public List<TMany> Items { get; private set; }

    public void LoadItems(TMany[] items)
    {
        Items.Clear();
        Items.AddRange(items);
        Refresh(false, true);
    }

    private static string GetTag()
    {
        return typeof(TOne).Name + "." + typeof(TMany).Name;
    }

    #region IDynamicEditorPage

    public DynamicEditorGrid EditorGrid { get; set; }

    public PageType PageType => PageType.Other;

    public bool Ready { get; set; }

    private bool _readOnly;
    public bool ReadOnly
    {
        get => _readOnly;
        set
        {
            if (_readOnly != value)
            {
                _readOnly = value;
                Reconfigure();
            }
        }
    }

    public virtual void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
    {
        Reconfigure();

        Item = (TOne)item;

        Refresh(true, false);

        var data = PageDataHandler?.Invoke(typeof(TMany));

        if (data == null)
        {
            if (Item.ID == Guid.Empty)
            {
                data = new CoreTable();
                data.LoadColumns(typeof(TMany));
            }
            else
            {
                var criteria = new Filters<TMany>();
                var exp = CoreUtils.GetPropertyExpression<TMany>(property.Name + ".ID");
                criteria.Add(new Filter<TMany>(exp).IsEqualTo(Item.ID).And(exp).IsNotEqualTo(Guid.Empty));
                criteria.AddRange(Criteria.Items);
                var sort = LookupFactory.DefineSort<TMany>();

                var columns = DynamicGridUtils.LoadEditorColumns(DataColumns());

                data = Client.Query(criteria.Combine(), columns, sort);

                LoadedColumns = columns.ColumnNames().ToHashSet();
            }
        }

        MasterList = data.ToArray<TMany>();

        Items = MasterList.ToList();
        Refresh(false, true);
        Ready = true;
    }
    public void Cancel()
    {
        foreach(var item in MasterList)
        {
            item.CancelChanges();
        }
        Items = MasterList.ToList();
        Refresh(false, true);
    }

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

    public virtual void AfterSave(object item)
    {
        if (IsAutoEntity)
        {
            return;
        }
        // First remove any deleted files
        foreach (var map in MasterList)
            if (!Items.Contains(map))
                OnDeleteItem(map);

        foreach (var map in Items)
        {
            var prop = (property.GetValue(map) as IEntityLink)!;
            prop.ID = Item.ID;
            prop.Synchronise(Item);
        }

        new Client<TMany>().Save(Items.Where(x => x.IsChanged()), "Updated by User");

        MasterList = Items.ToArray();
    }

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

    public string Caption()
    {
        var caption = typeof(TMany).GetCustomAttribute(typeof(Caption));
        if (caption != null)
            return ((Caption)caption).Text;
        var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(typeof(TMany).Name);
        return result;
    }

    public virtual int Order { get; set; } = int.MinValue;

    #endregion

    #region DynamicGrid

    protected virtual void OnDeleteItem(TMany item)
    {
        if (IsAutoEntity)
        {
            return;
        }
        Client.Delete(item, typeof(TMany).Name + " Deleted by User");
    }

    protected override CoreTable LoadImportKeys(string[] fields)
    {
        var result = base.LoadImportKeys(fields);
        result.LoadRows(MasterList);
        return result;
    }

    protected override bool CustomiseImportItem(TMany item)
    {
        var result = base.CustomiseImportItem(item);
        if (result)
        {
            var prop = (property.GetValue(item) as IEntityLink)!;
            prop.ID = Item.ID;
            prop.Synchronise(Item);
        }

        return result;
    }
    public override DynamicGridColumns GenerateColumns()
    {
        var cols = new DynamicGridColumns();
        cols.AddRange(base.GenerateColumns().Where(x => !x.ColumnName.StartsWith(property.Name + ".")));
        return cols;
    }

    protected override DynamicGridColumns LoadColumns()
    {
        return ColumnsComponent.LoadColumns();
    }

    protected override void SaveColumns(DynamicGridColumns columns)
    {
        ColumnsComponent.SaveColumns(columns);
    }
    protected override void LoadColumnsMenu(ContextMenu menu)
    {
        base.LoadColumnsMenu(menu);
        ColumnsComponent.LoadColumnsMenu(menu);
    }

    protected override DynamicGridSettings LoadSettings()
    {
        var tag = GetTag();

        var user = Task.Run(() => new UserConfiguration<DynamicGridSettings>(tag).Load());
        user.Wait();

        return user.Result;
    }
    protected override void SaveSettings(DynamicGridSettings settings)
    {
        var tag = GetTag();
        new UserConfiguration<DynamicGridSettings>(tag).Save(settings);
    }

    public override TMany CreateItem()
    {
        var result = new TMany();
        var prop = (property.GetValue(result) as IEntityLink)!;
        prop.ID = Item.ID;
        prop.Synchronise(Item);
        return result;
    }

    public override TMany LoadItem(CoreRow row)
    {
        return Items[_recordmap[row].Index];
    }

    public override void SaveItem(TMany item)
    {
        if (!Items.Contains(item))
            Items.Add(item);

        if (item is ISequenceable) Items = Items.AsQueryable().OrderBy(x => (x as ISequenceable)!.Sequence).ToList();
    }

    public override void DeleteItems(params CoreRow[] rows)
    {
        var items = rows.Select(LoadItem).ToList();
        foreach (var item in items)
        {
            Items.Remove(item);
        }
    }


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

        this.EnsureColumns(columns);

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

        action.Invoke(results, null);
    }

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

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

    private void AuditTrailClick(object sender, object? item)
    {
        if (item is not TMany entity) return;

        var window = new AuditWindow(entity.ID);
        window.ShowDialog();
    }

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

    protected override bool BeforeCopy(IList<TMany> items)
    {
        if (!base.BeforeCopy(items)) return false;

        for(int i = 0; i < items.Count; ++i)
        {
            var newItem = items[i].Clone();
            newItem.ID = Guid.Empty;
            items[i] = newItem;
        }
        return true;
    }

    #endregion

}