using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.HLE.FileSystem;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Application = Avalonia.Application;

namespace Ryujinx.Ava.UI.ViewModels
{
    public partial class DownloadableContentManagerViewModel : BaseModel
    {
        private readonly ApplicationLibrary _applicationLibrary;
        private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
        [ObservableProperty] private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
        [ObservableProperty] private AvaloniaList<DownloadableContentModel> _views = new();
        [ObservableProperty] private bool _showBundledContentNotice = false;

        private string _search;
        private readonly ApplicationData _applicationData;
        private readonly IStorageProvider _storageProvider;

        public AvaloniaList<DownloadableContentModel> DownloadableContents
        {
            get => _downloadableContents;
            set
            {
                _downloadableContents = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(UpdateCount));
                Sort();
            }
        }

        public string Search
        {
            get => _search;
            set
            {
                _search = value;
                OnPropertyChanged();
                Sort();
            }
        }

        public string UpdateCount
        {
            get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
        }

        public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData)
        {
            _applicationLibrary = applicationLibrary;

            _applicationData = applicationData;

            _storageProvider = RyujinxApp.MainWindow.StorageProvider;

            LoadDownloadableContents();
        }

        private void LoadDownloadableContents()
        {
            var dlcs = _applicationLibrary.DownloadableContents.Items
                .Where(it => it.Dlc.TitleIdBase == _applicationData.IdBase);

            bool hasBundledContent = false;
            foreach ((DownloadableContentModel dlc, bool isEnabled) in dlcs)
            {
                DownloadableContents.Add(dlc);
                hasBundledContent = hasBundledContent || dlc.IsBundled;

                if (isEnabled)
                {
                    SelectedDownloadableContents.Add(dlc);
                }

                OnPropertyChanged(nameof(UpdateCount));
            }

            ShowBundledContentNotice = hasBundledContent;

            Sort();
        }

        public void Sort()
        {
            DownloadableContents
                // Sort bundled last
                .OrderBy(it => it.IsBundled ? 0 : 1)
                .ThenBy(it => it.TitleId)
                .AsObservableChangeSet()
                .Filter(Filter)
                .Bind(out var view).AsObservableList();

            // NOTE(jpr): this works around a bug where calling _views.Clear also clears SelectedDownloadableContents for
            // some reason. so we save the items here and add them back after
            var items = SelectedDownloadableContents.ToArray();
            
            Views.Clear();
            Views.AddRange(view);

            foreach (DownloadableContentModel item in items)
            {
                SelectedDownloadableContents.ReplaceOrAdd(item, item);
            }

            OnPropertyChanged(nameof(Views));
        }

        private bool Filter<T>(T arg)
        {
            if (arg is DownloadableContentModel content)
            {
                return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleIdStr.ToLower().Contains(_search.ToLower());
            }

            return false;
        }

        public async void Add()
        {
            var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
            {
                Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
                AllowMultiple = true,
                FileTypeFilter = new List<FilePickerFileType>
                {
                    new("NSP")
                    {
                        Patterns = new[] { "*.nsp" },
                        AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" },
                        MimeTypes = new[] { "application/x-nx-nsp" },
                    },
                },
            });

            var totalDlcAdded = 0;
            foreach (var file in result)
            {
                if (!AddDownloadableContent(file.Path.LocalPath, out var newDlcAdded))
                {
                    await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
                }

                totalDlcAdded += newDlcAdded;
            }

            if (totalDlcAdded > 0)
            {
                await ShowNewDlcAddedDialog(totalDlcAdded);
            }
        }

        private bool AddDownloadableContent(string path, out int numDlcAdded)
        {
            numDlcAdded = 0;

            if (!File.Exists(path))
            {
                return false;
            }

            if (!_applicationLibrary.TryGetDownloadableContentFromFile(path, out var dlcs) || dlcs.Count == 0)
            {
                return false;
            }

            var dlcsForThisGame = dlcs.Where(it => it.TitleIdBase == _applicationData.IdBase).ToList();
            if (dlcsForThisGame.Count == 0)
            {
                return false;
            }

            foreach (var dlc in dlcsForThisGame)
            {
                if (!DownloadableContents.Contains(dlc))
                {
                    DownloadableContents.Add(dlc);
                    SelectedDownloadableContents.ReplaceOrAdd(dlc, dlc);

                    numDlcAdded++;
                }
            }

            if (numDlcAdded > 0)
            {
                OnPropertyChanged(nameof(UpdateCount));
                Sort();
            }

            return true;
        }

        public void Remove(DownloadableContentModel model)
        {
            SelectedDownloadableContents.Remove(model);

            if (!model.IsBundled)
            {
                DownloadableContents.Remove(model);
                OnPropertyChanged(nameof(UpdateCount));
                Sort();
            }
        }

        public void RemoveAll()
        {
            SelectedDownloadableContents.Clear();
            DownloadableContents.RemoveMany(DownloadableContents.Where(it => !it.IsBundled));

            OnPropertyChanged(nameof(UpdateCount));
            Sort();
        }

        public void EnableAll()
        {
            SelectedDownloadableContents.Clear();
            SelectedDownloadableContents.AddRange(DownloadableContents);
        }

        public void DisableAll()
        {
            SelectedDownloadableContents.Clear();
        }

        public void Enable(DownloadableContentModel model)
        {
            SelectedDownloadableContents.ReplaceOrAdd(model, model);
        }

        public void Disable(DownloadableContentModel model)
        {
            SelectedDownloadableContents.Remove(model);
        }

        public void Save()
        {
            var dlcs = DownloadableContents.Select(it => (it, SelectedDownloadableContents.Contains(it))).ToList();
            _applicationLibrary.SaveDownloadableContentsForGame(_applicationData, dlcs);
        }

        private Task ShowNewDlcAddedDialog(int numAdded)
        {
            var msg = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowDlcAddedMessage], numAdded);
            return Dispatcher.UIThread.InvokeAsync(async () =>
            {
                await ContentDialogHelper.ShowTextDialog(
                    LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], 
                    msg, 
                    string.Empty, 
                    string.Empty, 
                    string.Empty, 
                    LocaleManager.Instance[LocaleKeys.InputDialogOk], 
                    (int)Symbol.Checkmark);
            });
        }
    }
}
