using Insight.Domain.Enums;
using Insight.Domain.Network.Remote.Messages;
using Insight.Remote.Shared.Abstractions;
using Insight.Remote.Shared.Messages;
using Insight.Remote.Shared.Native.Windows;
using Insight.Remote.Windows.ViewModels;
using Insight.Remote.Windows.Views;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Windows;
using Vaitr.Bus;

namespace Insight.Remote.Windows.Services;

internal class WinApp : IDesktopApp
{
    private MainWindow? _mainWindow;
    private bool _disposed;

    private readonly IInputProvider _inputProvider;
    private readonly ICursorProvider _cursorProvider;
    private readonly IAudioProvider _audioProvider;
    private readonly IClipboardProvider _clipboardProvider;
    private readonly IDispatcher _dispatcher;
    private readonly MainViewModel _maiViewModel;
    private readonly Bus _bus;
    private readonly ILogger<WinApp> _logger;

    public WinApp(
        IInputProvider inputProvider,
        ICursorProvider cursorProvider,
        IAudioProvider audioProvider,
        IClipboardProvider clipboardProvider,
        IDispatcher dispatcher,
        MainViewModel mainViewModel,
        Bus bus,
        ILogger<WinApp> logger)
    {
        _inputProvider = inputProvider;
        _cursorProvider = cursorProvider;
        _audioProvider = audioProvider;
        _clipboardProvider = clipboardProvider;
        _dispatcher = dispatcher;
        _maiViewModel = mainViewModel;
        _bus = bus;
        _logger = logger;
    }

    public async Task InitAsync(RemoteControlMode mode, CancellationToken cancellationToken)
    {
        // subscriptions
        using var requestToken = _bus.SubscribeAsync<CastRequest>(OnCastRequestAsync, null);

        // maintain list of background tasks
        var tasks = new List<Task>
        {
            InvokeEventsAsync(cancellationToken),

            _inputProvider.InitAsync(cancellationToken),
            _audioProvider.InitAsync(cancellationToken),
            _clipboardProvider.InitAsync(cancellationToken)
        };

        // switch user modes
        if (mode == RemoteControlMode.Attended)
        {
            // generate and show main window
            await _dispatcher.InvokeAsync(() =>
            {
                _mainWindow = new MainWindow(_maiViewModel);
                _mainWindow.Show();
            }, cancellationToken);
        }
        else
        {
            if (Win32Interop.GetCurrentDesktop(out var currentDesktopName) is false)
            {
                _logger.LogWarning("Failed to get initial desktop name.");
            }
            else
            {
                _logger.LogInformation("Setting initial desktop to {currentDesktopName}.", currentDesktopName);
            }

            if (Win32Interop.SwitchToInputDesktop() is false)
            {
                _logger.LogWarning("Failed to set initial desktop.");
            }
        }

        // await tasks => rewrite thread of clipboard
        foreach (var task in tasks)
        {
            try
            {
                await task;
            }
            catch (TaskCanceledException) { }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }

    private async Task InvokeEventsAsync(CancellationToken cancellationToken)
    {
        SystemEvents.SessionSwitch += OnSessionSwitch;
        SystemEvents.SessionEnding += OnSessionEnding;

        var queue = new ConcurrentQueue<object>();

        using var switchToken = _bus.Subscribe<SessionSwitchEventArgs>(queue.Enqueue, null);
        using var endingToken = _bus.Subscribe<SessionEndingEventArgs>(queue.Enqueue, null);

        while (cancellationToken.IsCancellationRequested is false)
        {
            try
            {
                if (queue.TryDequeue(out var result) is false)
                {
                    await Task.Delay(1000, cancellationToken);
                    continue;
                }

                if (result is SessionSwitchEventArgs switchArgs)
                {
                    _logger.LogInformation("Session changing. Reason: {reason}", switchArgs.Reason);

                    var reason = (Domain.Enums.SessionSwitchReason)(int)switchArgs.Reason;
                    await _bus.PublishAsync(new WindowsSessionSwitched(reason, Process.GetCurrentProcess().SessionId), cancellationToken);
                }
                else if (result is SessionEndedEventArgs endingArgs)
                {
                    _logger.LogInformation("Session ending. Reason: {reason}", endingArgs.Reason);

                    var reason = (Domain.Enums.SessionEndReasons)endingArgs.Reason;
                    await _bus.PublishAsync(new WindowsSessionEnding(reason), cancellationToken);
                }
            }
            catch (TaskCanceledException) { }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }

        SystemEvents.SessionSwitch -= OnSessionSwitch;
        SystemEvents.SessionEnding -= OnSessionEnding;
    }

    private async ValueTask OnCastRequestAsync(CastRequest request, CancellationToken cancellationToken)
    {
        if (true) // test
        {
            await _bus.PublishAsync(new CastRequestDemand(request, true), cancellationToken);
            return;
        }

        if (request.Mode != RemoteControlMode.Unattended)
        {
            var result = await _dispatcher.InvokeAsync(() =>
            {
                return System.Windows.MessageBox.Show($"Accept connection request from {request.RequesterName}?", "Connection Request",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Question);
            }, cancellationToken);

            switch (result)
            {
                case MessageBoxResult.No: await _bus.PublishAsync(new CastRequestDemand(request, false), cancellationToken); return;
            }
        }

        await _bus.PublishAsync(new CastRequestDemand(request, true), cancellationToken);
    }

    private void OnSessionSwitch(object sender, SessionSwitchEventArgs e) => _bus.Publish(e);
    private void OnSessionEnding(object sender, SessionEndingEventArgs e) => _bus.Publish(e);

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {

        }

        _disposed = true;
    }
}