using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Avalonia.Controls.Selection;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;

using JetBrains.Annotations;
using NotNullAttribute = JetBrains.Annotations.NotNullAttribute;

namespace InABox.Avalonia
{
    
    public class CoreRepositoryItemCreatedArgs<TShell> : EventArgs
    {
        public TShell Item { get; private set; }

        public CoreRepositoryItemCreatedArgs(TShell item)
        {
            Item = item;
        }
    }

    public delegate void CoreRepositoryItemCreatedEvent<TShell>(object sender, CoreRepositoryItemCreatedArgs<TShell> args);
    
    
    public abstract class CoreRepository
    {
        public static bool IsCached(string? filename) => 
            !String.IsNullOrWhiteSpace(filename) 
            && File.Exists(CacheFileName(filename));
        
        public static string CacheFileName(string filename) =>
            Path.Combine(CacheFolder(), filename);

        public static string CacheFolder()
        {
            var result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            if (OperatingSystem.IsWindows())
            {
                var assembly = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
                result = Path.Combine(result, assembly);
            }

            if (CacheID != Guid.Empty)
                result = Path.Combine(result,CacheID.ToString());
            if (!Directory.Exists(result))
                Directory.CreateDirectory(result);
            return result;
        }

        public static Guid CacheID { get; set; }
        
    }

    public partial class CoreRepositoryFilter : ObservableObject
    {
        [ObservableProperty]
        private string? _name;
        
        [ObservableProperty]
        private string? _filter;
        
        [ObservableProperty]
        private bool _selected;

        public override string ToString() => Name ?? "";
    }
    
    public abstract class CoreRepository<TParent, TItem, TEntity> : CoreRepository, ICoreRepository, IEnumerable<TItem>
        where TParent : CoreRepository<TParent, TItem, TEntity>
        where TEntity : Entity, IRemotable, IPersistent, new() 
        where TItem : Shell<TParent,TEntity>, new()
    {
        readonly MultiQuery _query = new();
        
        public Func<Filter<TEntity>> Filter { get; set; }

        protected virtual Filter<TEntity>? BaseFilter() => null;
        
        public IModelHost Host { get; set; }
        
        private DateTime _lastUpdated = DateTime.MinValue;
        public DateTime LastUpdated
        {
            get => _lastUpdated;
            protected set
            {
                _lastUpdated = value;
                OnPropertyChanged();
            }
        }
        
        public Func<string>? FileName { get; }

        
        private string DataFileName() => FileName != null
            ? $"{FileName.Invoke()}.data"
            : string.Empty;
        
        private string FilterFileName() => !string.IsNullOrWhiteSpace(FilterTag) && FileName != null
            ? $"{FileName.Invoke()}.filter"
            : string.Empty;
        
        protected CoreRepository(IModelHost host, Func<Filter<TEntity>> filter, Func<string>? filename = null)
        {
            AllItems = new CoreObservableCollection<TItem>();
            AllItems.CollectionChanged += (sender, args) => ItemsChanged(AllItems);
            // EnableSynchronization(AllItems);
            
            Items = new CoreObservableCollection<TItem>(); 
            // EnableSynchronization(Items);
            
            Reset();
            Host = host;
            Filter = filter;
            FileName = filename;

        }

        protected virtual void ItemsChanged(IEnumerable<TItem> items)
        {
        }

        // private void EnableSynchronization(IEnumerable items)
        // {
        //     // BindingBase.EnableCollectionSynchronization(items, null, 
        //     //     (collection, context, method, access) =>
        //     //     {
        //     //         lock (collection)
        //     //         {
        //     //             method?.Invoke();
        //     //         }
        //     //     }
        //     // );
        // }

        #region INotifyPropertyChanged
        
        public event PropertyChangedEventHandler? PropertyChanged;

        protected void DoPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            PropertyChanged?.Invoke(sender, args);
        }
        
        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) 
                return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
            => DoPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        
        #endregion
        
        #region Image Lookups
        
        public Dictionary<Guid, byte[]> Images { get; private set; } = new Dictionary<Guid, byte[]>();

        public byte[]? GetImageSource(Guid id)
        {
            return Images.GetValueOrDefault(id);
        }
        
        public byte[]? GetImage(Guid id) => Images.GetValueOrDefault(id);
        
        public bool HasImages() => Images.Any();

        #endregion
        
        protected virtual string FilterTag => typeof(TEntity).EntityName().Split('.').Last();
        
        public CoreObservableCollection<CoreRepositoryFilter> AvailableFilters { get; } = new();
        IEnumerable ICoreRepository.AvailableFilters => AvailableFilters;
        
        protected Filter<TEntity>? SelectedFilter =>
            Serialization.Deserialize<Filter<TEntity>>(AvailableFilters.FirstOrDefault(x => x.Selected)?.Filter);
        
        public bool FiltersVisible => AvailableFilters.Any();

        public string? SelectedFilterName => AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;
        
        public void SelectFilter(String? name)
        {
            var definition = AvailableFilters.FirstOrDefault(x => String.Equals(x.Name, name));
            foreach (var availableFilter in AvailableFilters)
                availableFilter.Selected = definition == availableFilter;
            OnPropertyChanged(nameof(AvailableFilters));
        }
        
        protected Filter<TEntity>? EffectiveFilter()
        {
            var filters = new Filters<TEntity>();
            filters.Add(BaseFilter());
            filters.Add(Filter?.Invoke());
            filters.Add(SelectedFilter);
            var result = filters.Combine();
            return result;
        }
        
        protected Columns<TOtherEntity> GetColumns<TOtherItem, TOtherEntity>()
            where TOtherItem : Shell<TParent, TOtherEntity>, new()
            where TOtherEntity : Entity, IRemotable, IPersistent, new()
        {
            return new TOtherItem().Columns.Columns;
        }

        protected virtual void Initialize()
        {
            Loaded = false;
            AllItems.Clear();
            Items.Clear();
            Images.Clear();
        }
        
        
        
        public bool Loaded { get; protected set; }
        
        private void DoRefresh(bool force)
        {
            // force = force || Host.Status == ConnectionStatus.Connected;

            var selectedIDs = SelectedItems.Select(x => x.ID).ToHashSet();
            Items.Clear();

            var dataFileName = DataFileName();
            if (!force && !Loaded && CoreRepository.IsCached(dataFileName))
            {
                DoBeforeLoad();
                if (LoadFromStorage())
                {
                    DoAfterLoad();
                    foreach(var item in Items)
                    {
                        item.IsSelected = selectedIDs.Contains(item.ID);
                    }
                    return;
                }
            }
            
            if ((force || !Loaded) && (Host.Status == ConnectionStatus.Connected))
            {
                DoLoad();
                SaveToStorage();
                foreach(var item in Items)
                {
                    item.IsSelected = selectedIDs.Contains(item.ID);
                }
                return;
            }
            foreach(var item in Items)
            {
                item.IsSelected = selectedIDs.Contains(item.ID);
            }
        }

        private void AfterRefresh()
        {
            Loaded = true;
            Dispatcher.UIThread.Invoke(Search);
            NotifyChanged();
        }

        public virtual ICoreRepository Refresh(bool force)
        {
            DoRefresh(force);
            AfterRefresh();
            return this;
        }
        
        public void Refresh(bool force, Action loaded)
        {
            Task.Run(
                () =>
                {
                    DoRefresh(force);
                    Dispatcher.UIThread.Post(
                        () =>
                        {
                            AfterRefresh();
                            loaded?.Invoke();
                        }
                    );
                }
            );
        }

        public Task<ICoreRepository> RefreshAsync(bool force)
        {
            return Task.Run(() => Refresh(force));
        }
        
        public void Reset()
        {
            Initialize();
        }
        
        public event CoreRepositoryChangedEvent? Changed;
        protected void NotifyChanged() => Changed?.Invoke(this, new CoreRepositoryChangedEventArgs());
        
        public virtual SortOrder<TEntity>? Sort => LookupFactory.DefineSort<TEntity>();

        protected CoreObservableCollection<TItem> AllItems { get; private set; }
        
        private CoreTable _table = new CoreTable();

        public CoreObservableCollection<TItem> Items { get; private set; }

        public int ItemCount => Items.Count;
        
        IEnumerable ICoreRepository.Items => Items;

        #region Item Selection

        IEnumerable ICoreRepository.SelectedItems => SelectedItems;

        IEnumerable<TItem> SelectedItems => Items.Where(x => x.IsSelected);

        public bool IsSelected(TItem? item) => item is not null && item.IsSelected;
        
        public void SetSelectedItems(IEnumerable<TItem> items)
        {
            var selectedItems = items.Select(x => x.ID).ToHashSet();
            foreach(var item in Items)
            {
                item.IsSelected = selectedItems.Contains(item.ID);
            }
            Search();
        }
        
        public void SelectItem([CanBeNull] TItem? item)
        {
            if ((item != null) && !item.IsSelected)
            {
                item.IsSelected = true;
                Search();
            }
        }
        
        public void UnselectItem([CanBeNull] TItem? item)
        {
            if ((item != null) && item.IsSelected)
            {
                item.IsSelected = false;
                Search();
            }
        }
        
        public void ToggleSelection(TItem? item)
        {
            if (IsSelected(item))
                UnselectItem(item);
            else
                SelectItem(item);
        }
        
        public void SelectNone()
        {
            foreach(var item in AllItems)
            {
                item.IsSelected = false;
            }
            Search();
        }

        public void SelectAll()
        {
            foreach(var item in AllItems)
            {
                item.IsSelected = true;
            }
            Search();
        }
        
        void ICoreRepository.SelectItem(object item) => SelectItem(item as TItem);
        void ICoreRepository.UnselectItem(object item) => UnselectItem(item as TItem);
        void ICoreRepository.ToggleSelection(object item) => ToggleSelection(item as TItem);
        bool ICoreRepository.IsSelected(object item) => IsSelected(item as TItem);
        void ICoreRepository.SetSelectedItems(IEnumerable<object> items) => SetSelectedItems(items.OfType<TItem>());
        
        #endregion
        
        #region Searching
        
        public Func<TItem, bool>? SearchPredicate { get; set; }
        
        public Func<List<TItem>,List<TItem>>? SortPredicate { get; set; }

        public ICoreRepository Search(Func<TItem, bool> searchpredicate, Func<List<TItem>,List<TItem>> sortpredicate)
        {
            SortPredicate = sortpredicate;
            SearchPredicate = searchpredicate;
            Search();
            return this;
        }
        
        public ICoreRepository Search(Func<TItem, bool>? searchpredicate)
        {
            SearchPredicate = searchpredicate;
            Search();
            return this;
        }

        public ICoreRepository Search()
        {
            var items = AllItems == null
                ? new List<TItem>()
                : SearchPredicate != null
                    ? new List<TItem>(AllItems.Where(SearchPredicate))
                    : new List<TItem>(AllItems);
            
            if (SortPredicate != null)
                items = SortPredicate(items);
            
            Items.ReplaceRange(items);
            OnPropertyChanged(nameof(Items));
            OnPropertyChanged(nameof(ItemCount));
            return this;
        }
 
        ICoreRepository ICoreRepository.Search(Func<object,bool> method)
            => Search((o) => method(o as TItem));


        #endregion
        
        protected virtual Expression<Func<TEntity, object?>>? ImageColumn => null;
        
        #region Loading
        
        
        private void DoBeforeLoad()
        {
            _query.Clear();
            
            _query.Add(
                EffectiveFilter(),
                GetColumns<TItem,TEntity>(),
                Sort
            );
            
            if (ImageColumn is not null)
            {
                _query.Add(
                    new Filter<Document>(x => x.ID).InQuery(EffectiveFilter(), ImageColumn),
                    Columns.None<Document>().Add(x => x.ID)
                        .Add(x => x.Data)
                );
            }
            
            
        }  
        
        protected virtual void BeforeLoad(MultiQuery query)
        {
            
        }        
        
        protected virtual void AfterLoad(MultiQuery query)
        {
        }
        
        protected void DoLoad()
        {

            try
            {
                var selected = AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;

                if (!string.IsNullOrWhiteSpace(FilterTag))
                {
                    var filters = new GlobalConfiguration<CoreFilterDefinitions>(FilterTag).Load()
                        .Where(x => x.Visibility == CoreFilterDefinitionVisibility.DesktopAndMobile)
                        .Select(x => new CoreRepositoryFilter() { Name = x.Name, Filter = x.Filter, Selected = string.Equals(x.Name,selected) })
                        .ToList();
                    if (filters.Any())
                        filters.Insert(0, new CoreRepositoryFilter() { Name="All", Filter = "", Selected = !filters.Any(x => string.Equals(x.Name, selected)) });
                    AvailableFilters.ReplaceRange(filters);
                    Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
                }
                

                DoBeforeLoad();
                BeforeLoad(_query);
                
                Task.Run(() =>
                {
                    _query.Query();
                    DoAfterLoad();
                }).Wait();
                    
                AfterLoad(_query);
                Search();
                LastUpdated = DateTime.Now;
            }
            catch (Exception e)
            {
                MobileLogging.Log(e,"CoreRepository");
            }

        }
        
        protected void DoAfterLoad()
        {
            _table = _query.Get<TEntity>();
            AllItems.ReplaceRange(_query.Get<TEntity>().Rows.Select(CreateItem<TItem>));
                    
            if (ImageColumn != null)
            {
                Images.Clear();
                _query.Get<Document>().IntoDictionary<Document, Guid, byte[]>(Images, x => x.ID,
                    r => r.Get<Document, byte[]>(x => x.Data));
            }
           
        }
        
        #endregion
        
        #region Persistent Storage
        
        protected void InitializeTables()
        {
            var defs = _query.Definitions();
            foreach (var def in defs)
            {
                var table = InitializeTable(def.Value);
                _query.Set(def.Key, table);
            }
        }

        protected CoreTable InitializeTable(IQueryDef def)
        {
            var table = new CoreTable();
            if (def.Columns != null)
                table.LoadColumns(def.Columns);
            else
                table.LoadColumns(def.Type);
            return table;
        }

        protected class QueryStorage : ISerializeBinary
        {
            private readonly Dictionary<String, CoreTable> _data = new Dictionary<string, CoreTable>();

            public CoreTable Get([NotNull] String key) => _data[key];
            
            public void Set([NotNull] String key, CoreTable table) => _data[key] = table;

            public bool Contains([NotNull] String key) => _data.ContainsKey(key);

            public bool TryGet(string key, [NotNullWhen(true)] out CoreTable? table)
            {
                return _data.TryGetValue(key, out table);
            }
            
            public void SerializeBinary(CoreBinaryWriter writer)
            {
               writer.Write(_data.Count);
               foreach (var key in _data.Keys)
               {
                   writer.Write(key);
                   _data[key].SerializeBinary(writer);
               }
            }

            public void DeserializeBinary(CoreBinaryReader reader)
            {
                int count = reader.ReadInt32();
                for (int i = 0; i < count; i++)
                {
                    String key = reader.ReadString();
                    CoreTable table = new CoreTable();
                    table.DeserializeBinary(reader);
                    _data[key] = table;
                }
            }
        }
        
        protected bool LoadFromStorage()
        {
            var filterFileName = FilterFileName();
            if (!filterFileName.IsNullOrWhiteSpace())
            {
                if(CacheManager.TryLoadJSON<ObservableCollection<CoreRepositoryFilter>>(filterFileName, out var filters, out var _))
                {
                    AvailableFilters.ReplaceRange(filters);
                    Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
                }
            }

            var dataFileName = DataFileName();
            if (dataFileName.IsNullOrWhiteSpace())
            {
                InitializeTables();
                return true;
            }

            if(CacheManager.TryLoadBinary<QueryStorage>(dataFileName, out var storage, out var lastUpdated))
            {
                LastUpdated = lastUpdated;
                var defs = _query.Definitions();
                foreach (var key in defs.Keys)
                {
                    var keyStr = key.ToString() ?? "";
                    if(!storage.TryGet(keyStr, out var table))
                    {
                        table = InitializeTable(defs[key]);
                    }
                    var queryDef = _query.Definitions()[key];
                    if (CheckColumns(table, queryDef.Type, queryDef.Columns))
                        _query.Set(key, table);
                    else
                        return false;
                }
            }
            else
            {
                InitializeTables();
            }
            
            return true;
        }

        private bool CheckColumns(CoreTable table, Type T, IColumns? required)
        {
            required = CoreUtils.GetColumns(T, required);
            foreach (var column in required.ColumnNames())
            {
                if (!table.Columns.Any(x => String.Equals(x.ColumnName, column)))
                    return false;
            }

            return true;
        }

        protected void SaveToStorage()
        {
            
            var filterFileName = FilterFileName();
            if (!string.IsNullOrWhiteSpace(filterFileName))
            {
                CacheManager.SaveJSON(filterFileName, AvailableFilters, DateTime.MaxValue);
            }
            
            var dataFileName = DataFileName();
            if (dataFileName.IsNullOrWhiteSpace())
                return;
            
            QueryStorage storage = new QueryStorage();
            var results = _query.Results();
            foreach (var key in results.Keys)
                storage.Set(key.ToString() ?? "", results[key]);
            CacheManager.SaveBinary(dataFileName, storage, DateTime.MaxValue);
        }
        
        #endregion

        #region CRUD Operations
        
        public event CoreRepositoryItemCreatedEvent<TItem>? ItemAdded;
        
        private T CreateItem<T>(CoreRow row) 
            where T : Shell<TParent,TEntity>, new()
        {
            var result = new T() { Row = row, Parent = (TParent)this };
            result.PropertyChanged += (_, args) => DoPropertyChanged(result, args);
            return result;
        }

        public virtual TItem CreateItem()
        {
            
            CoreRow row = _table.NewRow();
            
            var entity = new TEntity();
            _table.FillRow(row,entity);

            var result = CreateItem<TItem>(row);
            ItemAdded?.Invoke(this, new CoreRepositoryItemCreatedArgs<TItem>(result));
            return result;
        }
        
        public virtual void CommitItem(TItem item)
        {
            _table.Rows.Add(item.Row);
            AllItems.Add(item);
            Search();
            NotifyChanged();
        }
        
        public virtual TItem AddItem()
        {
            var result = CreateItem();
            CommitItem(result);
            return result;
        }
        
        public virtual void DeleteItem(TItem item)
        {
            _table.Rows.Remove(item.Row);
            AllItems.Remove(item);
            Search();
            NotifyChanged();
        }

        public virtual void Save(string auditMessage)
        {
            new Client<TEntity>().Save(Items.Select(x=>x.Entity).Where(x=>x.IsChanged()),auditMessage);
        }
        public virtual Task SaveAsync(string auditMessage)
        {
            return Task.Run(() => Save(auditMessage));
        }

        object ICoreRepository.CreateItem() => this.CreateItem();
        
        void ICoreRepository.CommitItem(object item)
        {
            if (item is TItem titem)
                CommitItem(titem);
        }
        
        object ICoreRepository.AddItem() => this.AddItem();
        
        void ICoreRepository.DeleteItem(object item)
        {
            if (item is TItem titem)
                DeleteItem(titem);
        }
        
        #endregion
        
        #region IEnumerable Interface

        IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
        {
            return Items.GetEnumerator();
        }

        public IEnumerator GetEnumerator()
        {
            return Items.GetEnumerator();
        }
        
        #endregion


    }
    

    
}