using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using PCUT.Entities;
using PCUT.Entities.ApiResponse;
using PCUT.Extensions;
using PCUT.Models;
using Http.Core;
using Http.Core.Extensions;
using static Http.Core.Constants.HttpConstants;
using Windows.ApplicationModel.Core;
using Http.Core.Exceptions;

namespace PCUT.ViewModels
{
    internal class FileListViewModel : NotificationBase
    {
        public PaginationViewModel Pagination { get; }
        public MetadataViewModel MetadataModel { get; }

        private SelectableCollection<Category> _categories;
        public SelectableCollection<Category> Categories
        {
            get => _categories;
            set
            {
                if (_categories != null)
                    MetadataModel.UnbindPropertyChangedHandle(_categories);
                SetProperty(ref _categories, value ?? new SelectableCollection<Category>(new Category { Name = "-None-" }));
                MetadataModel.BindPropertyChangedHandle(_categories);
            }
        }

        public SelectableCollection<CdrFileModel> FilteredFiles { get; }
        private readonly IDictionary<string, ThumbnailSource> _thumbnailSources = new Dictionary<string, ThumbnailSource>();
        private readonly FilterModel _filterModel;

        private bool _filterMetadata;
        private readonly Predicate<CategoryMetadata>[] _metadataFilters = new Predicate<CategoryMetadata>[6];

        public FileListViewModel()
        {
            FilteredFiles = new SelectableCollection<CdrFileModel>();
            MetadataModel = new MetadataViewModel();

            _filterModel = new FilterModel();

            _metadataFilters[0] = type => _filterModel.TypeIds.Contains(type.Id);
            _metadataFilters[1] = brand => _filterModel.BrandIds.Contains(brand.Id);
            _metadataFilters[2] = serie => _filterModel.SeriesIds.Contains(serie.Id);
            _metadataFilters[3] = model => _filterModel.ModelIds.Contains(model.Id);
            _metadataFilters[4] = subType => _filterModel.SubTypeIds.Contains(subType.Id);
            _metadataFilters[5] = year => _filterModel.YearIds.Contains(year.Id);

            MetadataModel.MetadataChanged += OnMetadataChanged;
            MetadataModel.SetFilterChangedHandle();

            Pagination = new PaginationViewModel(withSearch: true);
            Pagination.TextSearched += async (sender, args) => await SearchFileAsync(args.Text);
            Pagination.PageChanged += OnPageChanged;
        }

        public void SetFilterMetadataOnFileChange()
        {
            _filterMetadata = true;
        }

        public async Task GetCategoriesAsync()
        {
            using (var client = HttpClientFactory.CreateClient(ClientNames.ApiClient))
            {
                var categories = await client.GetCategoriesAsync(filterByRole: true);
                Categories.Load(categories);
            }
        }

        public async Task Reload()
        {
            await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, Pagination.SearchText);
            var loadThumbnailTask = LoadThumbnailsAsync();
            if (_filterMetadata)
                FilterMetadata(null);
            await loadThumbnailTask;
        }

        private async void OnPageChanged(object sender, PageChangedEventArgs args)
        {
            await GetFilesByFilterAsync(args.Page, args.PageSize, Pagination.SearchText);
            var loadThumbnailTask = LoadThumbnailsAsync();
            //if (_filterMetadata)
            //    FilterMetadata(null);
            await loadThumbnailTask;
        }

        private async void OnMetadataChanged(object sender, MetadataChangedEventArgs args)
        {
            Pagination.PageChanged -= OnPageChanged;
            Pagination.Page = 1;
            _filterModel.Clear();
            await GetFilesAsync(args.MetadataType);
            Pagination.PageChanged += OnPageChanged;
        }

        public async Task GetFilesAsync(string metadataType = null)
        {
            await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, Pagination.SearchText);
            var loadThumbnailTask = LoadThumbnailsAsync();
            if (_filterMetadata)
                FilterMetadata(metadataType);
            await loadThumbnailTask;
        }

        public async Task SearchFileAsync(string searchText)
        {
            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
            {
                await GetFilesByFilterAsync(Pagination.Page, Pagination.PageSize, searchText);
                await LoadThumbnailsAsync();
            });
        }

        private async Task GetFilesByFilterAsync(int page, int pageSize, string searchText)
        {
            var url = CreateFilterUrl(page, pageSize, searchText);
            using (var httpClient = HttpClientFactory.CreateClient(ClientNames.ApiClient))
            {
                try
                {
                    var response = await httpClient.GetAsync(url);
                    if (response.IsSuccessStatusCode)
                    {
                        var apiResponse = await response.DeserializeObjectAsync<PaginationResponse<IEnumerable<CdrFileModel>>>();
                        var data = apiResponse.Data;
                        int totalPages;
                        if (_filterModel.IsLoaded)
                        {
                            totalPages = apiResponse.Pagination.TotalPages;
                        }
                        else
                        {
                            totalPages = (int)Math.Ceiling((double)apiResponse.Pagination.TotalRecords / pageSize);
                            _filterModel.Load(data);
                            data = data.Take(pageSize);
                        }
                        Pagination.Page = apiResponse.Pagination.Page;
                        Pagination.TotalPage = totalPages;
                        Pagination.TotalRecords = apiResponse.Pagination.TotalRecords;
                        
                        int displayId = (apiResponse.Pagination.Page - 1) * pageSize + 1;
                        var files = data.Select(file =>
                        {
                            file.FileId = file.File?.Id;
                            file.ThumbnailSource = GetOrCreateThumbnailSource(file);
                            file.DisplayId = displayId.ToString();

                            displayId++;
                            return file;
                        });
                        FilteredFiles.Load(files);
                    }
                }
                catch (Exception ex) when (!(ex is AppOutdatedException))
                {

                }
            }
        }

        private string CreateFilterUrl(int page, int pageSize, string searchText)
        {
            if (!_filterModel.IsLoaded)
                pageSize = int.MaxValue;

            var filterBuilder = new StringBuilder();
            if (Categories.SelectedItem != default)
                filterBuilder.AppendFilter("category", Categories.SelectedItem.Id);
            if (MetadataModel.Brands.SelectedItem != default)
                filterBuilder.AppendFilter("brand", MetadataModel.Brands.SelectedItem.Id);
            if (MetadataModel.Types.SelectedItem != default)
                filterBuilder.AppendFilter("type", MetadataModel.Types.SelectedItem.Id);
            if (MetadataModel.Models.SelectedItem != default)
                filterBuilder.AppendFilter("_model", MetadataModel.Models.SelectedItem.Id);
            if (MetadataModel.Series.SelectedItem != default)
                filterBuilder.AppendFilter("series", MetadataModel.Series.SelectedItem.Id);
            if (MetadataModel.Years.SelectedItem != default)
                filterBuilder.AppendFilter("year", MetadataModel.Years.SelectedItem.Id);
            if (MetadataModel.SubTypes.SelectedItem != default)
                filterBuilder.AppendFilter("subType", MetadataModel.SubTypes.SelectedItem.Id);

            if (!string.IsNullOrEmpty(searchText))
                filterBuilder.AppendFilter("name", "cn", searchText);
            
            var filter = filterBuilder.BuildFilter().ToString();

            return PathBuilder.FromRouteTemplate(Api.CdrFiles)
                              .AddQuery("filter", filter)
                              .AddQuery("page", page.ToString())
                              .AddQuery("pageSize", pageSize.ToString())
                              .AddQuery("sort", "createdAt:desc")
                              .Build();
        }

        private void FilterMetadata(string metadataType)
        {
            MetadataModel.FilterAll(metadataType, _metadataFilters);
        }

        private ImageSource GetOrCreateThumbnailSource(CdrFileModel file)
        {
            if (file.Thumbnail == null)
                return null;

            if (_thumbnailSources.TryGetValue(file.Thumbnail.Id, out var thumbnail))
                return thumbnail.Source;

            thumbnail = new ThumbnailSource(new BitmapImage());
            _thumbnailSources[file.Thumbnail.Id] = thumbnail;
            return thumbnail.Source;
        }

        private async Task LoadThumbnailsAsync()
        {
            try
            {
                await Task.WhenAll(
                    _thumbnailSources.Where(x => !x.Value.IsLoaded).Select(x => x.Value.LoadAsync(x.Key)));
            }
            catch
            {
            }
        }

        public class CdrFileModel : CdrFile
        {
            public ImageSource ThumbnailSource { get; set; }
            public string FileId { get; set; }
            public string DisplayId { get; set; }
        }

        private class FilterModel
        {
            public readonly List<string> TypeIds = new List<string>();
            public readonly List<string> BrandIds = new List<string>();
            public readonly List<string> SeriesIds = new List<string>();
            public readonly List<string> ModelIds = new List<string>();
            public readonly List<string> SubTypeIds = new List<string>();
            public readonly List<string> YearIds = new List<string>();

            public bool IsLoaded { get; set; }

            public void Clear()
            {
                TypeIds.Clear();
                BrandIds.Clear();
                SeriesIds.Clear();
                ModelIds.Clear();
                SubTypeIds.Clear();
                YearIds.Clear();
                IsLoaded = false;
            }

            public void Load(IEnumerable<CdrFileModel> data)
            {
                TypeIds.AddRange(data.Select(x => x.Type?.Id).Where(x => x != null).Distinct());
                BrandIds.AddRange(data.Select(x => x.Brand?.Id).Where(x => x != null).Distinct());
                SeriesIds.AddRange(data.Select(x => x.Series?.Id).Where(x => x != null).Distinct());
                ModelIds.AddRange(data.Select(x => x.Model?.Id).Where(x => x != null).Distinct());
                SubTypeIds.AddRange(data.Select(x => x.SubType?.Id).Where(x => x != null).Distinct());
                YearIds.AddRange(data.Select(x => x.Year?.Id).Where(x => x != null).Distinct());
                IsLoaded = true;
            }
        }

        private class ThumbnailSource
        {
            public ImageSource Source { get; set; }
            public bool IsLoaded { get; set; }

            public ThumbnailSource(ImageSource source)
            {
                Source = source;
                IsLoaded = false;
            }

            public async Task LoadAsync(string id)
            {
                if (!IsLoaded)
                {
                    IsLoaded = true;
                    try
                    {
                        await LoadThumbnailAsync(id);
                    }
                    catch
                    {
                        IsLoaded = false;
                        throw;
                    }
                }
            }

            private async Task LoadThumbnailAsync(string thumbnailId)
            {
                using (var httpClient = HttpClientFactory.CreateClient(ClientNames.ApiClient))
                {
                    using (var stream = new MemoryStream())
                    {
                        var response = await httpClient.DownloadAsync(Api.Download.FormatRoute(thumbnailId), stream);
                        if (response)
                        {
                            stream.Seek(0, SeekOrigin.Begin);
                            await Source.LoadStreamAsync(stream.AsRandomAccessStream());
                        }
                    }
                }
            }
        }
    }
}
