using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Needlework.Net.Constants;
using Needlework.Net.Extensions;
using Needlework.Net.Helpers;
using Needlework.Net.Messages;
using Needlework.Net.Services;
using Needlework.Net.Views.MainWindow;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Needlework.Net.ViewModels.MainWindow;

public partial class MainWindowViewModel
    : ObservableObject, IRecipient<OopsiesDialogRequestedMessage>, IEnableLogger
{
    private readonly DocumentService _documentService;

    private readonly NotificationService _notificationService;

    private readonly DialogService _dialogService;

    private readonly SchemaPaneService _schemaPaneService;

    public MainWindowViewModel(DialogService dialogService, DocumentService documentService, NotificationService notificationService, SchemaPaneService schemaPaneService)
    {
        _dialogService = dialogService;
        _documentService = documentService;
        _notificationService = notificationService;
        _schemaPaneService = schemaPaneService;

        _notificationService.Notifications.Subscribe(async notification =>
        {
            var vm = new NotificationViewModel(notification);
            Notifications.Add(vm);
            await Task.Delay(notification.Duration ?? TimeSpan.FromSeconds(10));
            Notifications.Remove(vm);
        });

        _schemaPaneService.SchemaPaneItems.Subscribe(async item =>
        {
            try
            {
                var document = item.Tab switch
                {
                    Pages.Endpoints.Tab.LCU => await documentService.GetLcuSchemaDocumentAsync(),
                    Pages.Endpoints.Tab.GameClient => await documentService.GetLolClientDocumentAsync(),
                    _ => throw new NotImplementedException()
                };
                var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[item.Key], document.OpenApiDocument);
                var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
                if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
                {
                    Schemas.Add(schemaViewModel);
                    IsPaneOpen = true;

                    OpenSchemaPaneCommand.NotifyCanExecuteChanged();
                    CloseSchemaAllCommand.NotifyCanExecuteChanged();
                }
            }
            catch (Exception ex)
            {
                this.Log()
                    .Error(ex, "Failed to load schema pane item.");
                _notificationService.Notify("Schema pane", ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error);
                throw;
            }
        });

        WeakReferenceMessenger.Default.RegisterAll(this);
    }

    [ObservableProperty]
    private bool _isPaneOpen;

    [ObservableProperty]
    private ObservableCollection<SchemaViewModel> _schemas = [];

    [ObservableProperty]
    private SchemaViewModel? _selectedSchema;

    [ObservableProperty]
    private ObservableCollection<NotificationViewModel> _notifications = [];

    [ObservableProperty]
    private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails;

    public string AppName => AppInfo.Name;

    public string Title => $"{AppInfo.Name} {AppInfo.Version}";

    partial void OnSelectedSchemaSearchDetailsChanged(SchemaSearchDetailsViewModel? value)
    {
        if (value == null) return;
        Task.Run(async () =>
        {
            try
            {
                var document = value.Tab switch
                {
                    Pages.Endpoints.Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(),
                    Pages.Endpoints.Tab.GameClient => await _documentService.GetLolClientDocumentAsync(),
                    _ => throw new NotImplementedException()
                };
                var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[value.Key], document.OpenApiDocument);
                var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
                Dispatcher.UIThread.Post(() =>
                {
                    if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
                    {
                        Schemas.Add(schemaViewModel);
                        IsPaneOpen = true;

                        OpenSchemaPaneCommand.NotifyCanExecuteChanged();
                        CloseSchemaAllCommand.NotifyCanExecuteChanged();
                    }
                });
            }
            catch (Exception ex)
            {
                this.Log()
                    .Error(ex, "Failed to load schema search details.");
                _notificationService.Notify("Schema search", ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error);
                throw;
            }
        });
    }

    partial void OnSelectedSchemaChanged(SchemaViewModel? value)
    {
        CloseSchemaCommand.NotifyCanExecuteChanged();
    }

    partial void OnSchemasChanged(ObservableCollection<SchemaViewModel> value)
    {
        if (!value.Any())
        {
            IsPaneOpen = false;
        }
    }

    public async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
    {
        if (searchText == null) return [];

        try
        {
            var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync(cancellationToken);
            var gameClientDocument = await _documentService.GetLolClientDocumentAsync(cancellationToken);
            var lcuResults = lcuSchemaDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
                    .Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.LCU));
            var gameClientResults = gameClientDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
                    .Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.GameClient));
            return Enumerable.Concat(lcuResults, gameClientResults);
        }
        catch (Exception ex)
        {
            this.Log()
                .Error(ex, "Failed to load populate.");
            _notificationService.Notify("Schema search", ex.Message, FluentAvalonia.UI.Controls.InfoBarSeverity.Error);
            throw;
        }
    }

    [RelayCommand(CanExecute = nameof(CanOpenSchemaPane))]
    private void OpenSchemaPane()
    {
        IsPaneOpen = !IsPaneOpen;
    }

    private bool CanOpenSchemaPane()
    {
        return Schemas.Any();
    }

    [RelayCommand(CanExecute = nameof(CanCloseSchema))]
    private void CloseSchema()
    {
        if (SelectedSchema is SchemaViewModel selection)
        {
            SelectedSchema = null;
            Schemas = new ObservableCollection<SchemaViewModel>(Schemas.Where(schema => schema != selection));

            OpenSchemaPaneCommand.NotifyCanExecuteChanged();
            CloseSchemaCommand.NotifyCanExecuteChanged();
            CloseSchemaAllCommand.NotifyCanExecuteChanged();
        }
    }

    private bool CanCloseSchema()
    {
        return SelectedSchema != null;
    }

    [RelayCommand(CanExecute = nameof(CanCloseSchemaAll))]
    private void CloseSchemaAll()
    {
        SelectedSchema = null;
        Schemas = [];

        OpenSchemaPaneCommand.NotifyCanExecuteChanged();
        CloseSchemaCommand.NotifyCanExecuteChanged();
        CloseSchemaAllCommand.NotifyCanExecuteChanged();
    }

    private bool CanCloseSchemaAll()
    {
        return Schemas.Any();
    }

    [RelayCommand]
    private void OpenUrl(string url)
    {
        var process = new Process() { StartInfo = new ProcessStartInfo(url) { UseShellExecute = true } };
        process.Start();
    }

    public void Receive(OopsiesDialogRequestedMessage message)
    {
        Avalonia.Threading.Dispatcher.UIThread.Invoke(async () => await _dialogService.ShowAsync<OopsiesDialog>(message.Value));
    }
}
