using Insight.Domain.Enums;
using Insight.Domain.Interfaces;
using Insight.Domain.Network;
using Insight.Domain.Network.Remote.Messages;
using Insight.Remote.Shared.Messages;
using Insight.Remote.Shared.Services;
using Microsoft.Extensions.Logging;
using Vaitr.Bus;

namespace Insight.Remote.Shared.Network.Handlers;

public class RemoteHandler : IMessageHandler<RemoteSession>
{
    private readonly Bus _bus;
    private readonly Streamer _streamer;
    private readonly ILogger<RemoteHandler> _logger;

    public RemoteHandler(Bus bus, Streamer streamer, ILogger<RemoteHandler> logger)
    {
        _bus = bus;
        _streamer = streamer;
        _logger = logger;
    }

    public async ValueTask HandleAsync<TMessage>(RemoteSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage
    {
        if (message is RemoteSessionResponse sessionResponse)
        {
            await OnSessionResponse(sender, sessionResponse, cancellationToken);
        }
        else if (message is CastRequest castRequest)
        {
            await OnCastRequest(sender, castRequest, cancellationToken);
        }
        else if (message is CastAbort castAbort)
        {
            await OnCastAbort(sender, castAbort, cancellationToken);
        }
        else if (message is CastScreenReceived screenDataReceived)
        {
            await OnScreenDataReceived(sender, screenDataReceived, cancellationToken);
        }
        else if (message is CastCursorReceived cursorCallbackData)
        {
            await OnCursorDataReceived(sender, cursorCallbackData, cancellationToken);
        }
    }

    private async Task OnSessionResponse(RemoteSession session, RemoteSessionResponse sessionResponse, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Remote {session.Id} => SessionResponse");

        session.Id = sessionResponse.SessionId;

        await _bus.PublishAsync(new IdentityChanged(session.Id));
    }

    private async Task OnCastRequest(RemoteSession session, CastRequest castRequest, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Remote {session.Id} => CastRequest");

        //if (request.RequesterName is null || request.ConnectionId is null) return;

        var permitCast = false;

        switch (castRequest.Mode)
        {
            case RemoteControlMode.Unattended:
                {
                    if (session.AccessKey == castRequest.AccessKey) permitCast = true;
                    break;
                }
            case RemoteControlMode.Attended:
                {
                    // timeout source
                    var waiter = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                    // subscribe token for demand response
                    using var response = _bus.Subscribe<CastRequestDemand>((message) =>
                    {
                        if (message.Accepted is false) return;

                        permitCast = true;
                        waiter.Cancel();
                    }, null);

                    // send internal demand request
                    await _bus.PublishAsync(castRequest, cancellationToken);

                    // wait for timeout async
                    try { await Task.Delay(TimeSpan.FromSeconds(5), waiter.Token); }
                    catch (TaskCanceledException) { }
                    break;
                }
        }

        try
        {
            var response = new CastRequestResponse(castRequest)
            {
                Accepted = permitCast
            };

            await session.SendAsync(response, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }

        //DesktopStream? viewer = _viewerPool.Values.FirstOrDefault(p => p.ConnectionId == request.ConnectionId);

        //if (viewer is null)
        //{
        //    viewer = _viewerFactory.Create();
        //    _viewerPool.AddOrUpdate(viewer.Uid, viewer, (uid, v) => viewer);
        //}

        _ = InitViewerSession(castRequest, cancellationToken);
    }

    private async Task OnCastAbort(RemoteSession session, CastAbort castAbort, CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Remote {session.Id} => CastAbort");

        await _streamer.CancelAsync(castAbort, cancellationToken);
    }

    private async Task OnScreenDataReceived(RemoteSession session, CastScreenReceived screenDataReceived, CancellationToken cancellationToken)
    {
        //_logger.LogInformation($"Remote {session.Id} => ScreenDataReceived");

        await _streamer.FrameReceivedAsync(screenDataReceived, cancellationToken);
    }

    private async Task OnCursorDataReceived(RemoteSession session, CastCursorReceived cursorCallbackData, CancellationToken cancellationToken)
    {
        //_logger.LogInformation($"Remote {session.Id} => ScreenDataReceived");

        await _streamer.CursorReceivedAsync(cursorCallbackData, cancellationToken);
    }

    private async Task InitViewerSession(CastRequest castRequest, CancellationToken cancellationToken)
    {
        try
        {
            await _streamer.InitAsync(castRequest, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.ToString());
        }
    }
}