using Http.Core;
using PCUT.Entities;
using PCUT.Entities.ApiResponse;
using PCUT.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Http.Core.Extensions;
using Windows.UI.Xaml.Controls.Primitives;
using static Http.Core.Constants.HttpConstants;
using Http.Core.Exceptions;

namespace PCUT.ViewModels
{
    public class MetadataViewModel : NotificationBase
    {
        public event MetadataChangedEventHandler MetadataChanged;

        public const string TypeHeader = "Type";
        public const string BrandHeader = "Brand";
        public const string SerieHeader = "Series";
        public const string ModelHeader = "Model";
        public const string SubTypeHeader = "SubType";
        public const string YearHeader = "Year";

        public SelectableCollection<string> MetadataTypes { get; }
        
        public SelectableCollection<CategoryMetadata> Types { get; set; }
        public SelectableCollection<CategoryMetadata> Brands { get; set; }
        public SelectableCollection<CategoryMetadata> Series { get; set; }
        public SelectableCollection<CategoryMetadata> Models { get; set; }
        public SelectableCollection<CategoryMetadata> SubTypes { get; set; }
        public SelectableCollection<CategoryMetadata> Years { get; set; }

        private readonly List<SelectableCollection<CategoryMetadata>> _collections;

        public MetadataViewModel()
        {
            MetadataTypes = new SelectableCollection<string>(
                withDefault: false,
                TypeHeader,
                BrandHeader,
                SerieHeader,
                ModelHeader,
                SubTypeHeader,
                YearHeader
            );

            var defaultMetadata = new CategoryMetadata { Value = "-None-" };
            Types = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            Brands = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            Series = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            Models = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            SubTypes = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            Years = new SelectableCollection<CategoryMetadata>(defaultMetadata);
            _collections = new List<SelectableCollection<CategoryMetadata>> { Types, Brands, Series, Models, SubTypes, Years };

            MetadataChanged += OnMetadataChanged;
        }

        public void BindAll(params Selector[] selectors)
        {
            for (int i = 0; i < selectors.Length && i < _collections.Count; i++)
                _collections[i].Bind(selectors[i]);
        }

        public void FilterAll(string type = null, params Predicate<CategoryMetadata>[] filters)
        {
            var index = type == null ? -1 : MetadataTypes.Items.IndexOf(type);
            for (int i = index + 1; i < _collections.Count; i++)
                _collections[i].Filter(filters[i]);
        }

        public void UnselectAll(string type = null)
        {
            var index = type == null ? -1 : MetadataTypes.Items.IndexOf(type);
            for (int i = index + 1; i < _collections.Count; i++)
                _collections[i].SelectedIndex = -1;
        }

        public void SetFilterChangedHandle()
        {
            AssignHandler(OnPropertyChanged);
        }

        public void UnsetFilterChangedHandle()
        {
            RemoveHandler(OnPropertyChanged);
        }

        public void BindPropertyChangedHandle(SelectableCollection collection)
        {
            collection.PropertyChanged += OnPropertyChanged;
        }

        public void UnbindPropertyChangedHandle(SelectableCollection collection)
        {
            collection.PropertyChanged -= OnPropertyChanged;
        }

        private void AssignHandler(PropertyChangedEventHandler handler, string type = null)
        {
            var index = type == null ? -1 : MetadataTypes.Items.IndexOf(type);
            for (int i = index + 1; i < _collections.Count; i++)
                _collections[i].PropertyChanged += handler;
        }

        private void RemoveHandler(PropertyChangedEventHandler handler, string type = null)
        {
            var index = type == null ? -1 : MetadataTypes.Items.IndexOf(type);
            for (int i = index + 1; i < _collections.Count; i++)
                _collections[i].PropertyChanged -= handler;
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == nameof(SelectableCollection.SelectedIndex))
            {
                var metadataType = sender == Types ? TypeHeader
                                 : sender == Brands ? BrandHeader
                                 : sender == Series ? SerieHeader
                                 : sender == Models ? ModelHeader
                                 : sender == SubTypes ? SubTypeHeader
                                 : sender == Years ? YearHeader
                                 : null;
                RemoveHandler(OnPropertyChanged, metadataType);
                MetadataChanged?.Invoke(sender, new MetadataChangedEventArgs(metadataType, args));
                AssignHandler(OnPropertyChanged, metadataType);
            }
        }

        private void OnMetadataChanged(object sender, MetadataChangedEventArgs args)
        {
            UnselectAll(args.MetadataType);
        }

        public async Task LoadAllMetadataAsync()
        {
            await Task.WhenAll(
                LoadMetadataAsync(TypeHeader),
                LoadMetadataAsync(BrandHeader),
                LoadMetadataAsync(SerieHeader),
                LoadMetadataAsync(ModelHeader),
                LoadMetadataAsync(SubTypeHeader),
                LoadMetadataAsync(YearHeader)
            );
        }

        private async Task LoadMetadataAsync(string type)
        {
            var metadataList = await GetMetadataAsync(type);
            SelectableCollection<CategoryMetadata> collections = null;
            switch (type)
            {
                case TypeHeader:
                    collections = Types;
                    break;
                case BrandHeader:
                    collections = Brands;
                    break;
                case SerieHeader:
                    collections = Series;
                    break;
                case ModelHeader:
                    collections = Models;
                    break;
                case SubTypeHeader:
                    collections = SubTypes;
                    break;
                case YearHeader:
                    collections = Years;
                    break;
                default:
                    break;
            }
            collections?.Load(metadataList);
        }

        private static async Task<IEnumerable<CategoryMetadata>> GetMetadataAsync(string type)
        {
            using (var client = HttpClientFactory.CreateClient(ClientNames.ApiClient))
            {
                try
                {
                    var url = PathBuilder.FromRouteTemplate(Api.Metadata)
                                         .AddQuery("filter", $"[\"type:eq:{type.ToUpper()}\"]")
                                         .AddQuery("sort", "createdAt:desc")
                                         .Build();
                    var response = await client.GetAsync(url);
                    if (response.IsSuccessStatusCode)
                    {
                        var content = await response.DeserializeObjectAsync<PaginationResponse<IEnumerable<CategoryMetadata>>>();
                        return content.Data;
                    }
                }
                catch (Exception ex) when (!(ex is AppOutdatedException))
                {
                }
            }
            return Enumerable.Empty<CategoryMetadata>();
        }
    }

    public delegate void MetadataChangedEventHandler(object sender, MetadataChangedEventArgs args);
    public class MetadataChangedEventArgs : EventArgs
    {
        private readonly string _metadataType;
        public virtual string MetadataType
        {
            get => _metadataType;
        }

        private readonly PropertyChangedEventArgs _propertyChangedArgs;
        public virtual PropertyChangedEventArgs PropertyChangedArgs
        {
            get => _propertyChangedArgs;
        }

        public MetadataChangedEventArgs(string metadataType, PropertyChangedEventArgs propertyChangedArgs)
        {
            _metadataType = metadataType;
            _propertyChangedArgs = propertyChangedArgs;
        }
    }
}
