using Insight.Agent.Messages;
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Interfaces;
using Insight.Web.Messages;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Vaitr.MemoryBus;
using Vaitr.Network;

namespace Insight.Server.Network.Handlers.Web
{
    public class ConsoleProxyHandler : IWebMessageHandler<WebSession>
    {
        private readonly List<IDisposable> _subscriptions = new();

        private readonly TcpSessionPool<AgentSession, MemPackSerializer, IAgentMessage> _agentPool;
        private readonly TcpSessionPool<WebSession, MemPackSerializer, IWebMessage> _webPool;
        private readonly IMongoDatabase _database;
        private readonly IMemoryBus _bus;
        private readonly ILogger<ConsoleProxyHandler> _logger;

        public ConsoleProxyHandler(
            TcpSessionPool<AgentSession, MemPackSerializer, IAgentMessage> agentPool,
            TcpSessionPool<WebSession, MemPackSerializer, IWebMessage> webPool,
            IMongoDatabase database,
            IMemoryBus bus,
            ILogger<ConsoleProxyHandler> logger)
        {
            _agentPool = agentPool;
            _webPool = webPool;
            _database = database;
            _bus = bus;
            _logger = logger;

            _subscriptions.Add(_bus.SubscribeAsync<ConsoleQuery>(OnConsoleQueryAsync, null));
        }

        public async ValueTask HandleAsync<TMessage>(WebSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IWebMessage
        {
            if (message is ConsoleQueryProxyRequest consoleRequest)
            {
                await OnConsoleQueryRequestAsync(sender, consoleRequest, cancellationToken);
            }
        }

        private async ValueTask OnConsoleQueryRequestAsync(WebSession session, ConsoleQueryProxyRequest request, CancellationToken cancellationToken)
        {
            // get host
            var hostEntity = await _database.Host()
                .Find(Builders<HostEntity>
                .Filter
                .Eq(p => p.Id, request.HostId))
                .FirstOrDefaultAsync(cancellationToken);

            if (hostEntity is null)
            {
                _logger.LogWarning("hostEntity is null");
                return;
            }

            // get agent
            var agentEntity = await _database.Agent()
                .Find(Builders<AgentEntity>
                .Filter
                .Eq(p => p.Id, hostEntity.Agent))
                .FirstOrDefaultAsync(cancellationToken);

            if (agentEntity is null)
            {
                _logger.LogWarning("agentEntity is null");
                return;
            }

            // check if agent online
            if (_agentPool.FirstOrDefault(p => p.Value.Id == agentEntity.Id).Value is not AgentSession agent) return;

            // send "real" packet to agent
            await agent.SendAsync(new ConsoleQueryRequest
            {
                Id = request.Id,
                HostId = request.HostId,
                Query = request.Query
            }, cancellationToken);
        }

        private async ValueTask OnConsoleQueryAsync(ConsoleQuery query, CancellationToken cancellationToken)
        {
            // check if web online
            if (_webPool.FirstOrDefault().Value is not WebSession web) return;

            await web.SendAsync(new ConsoleQueryProxy
            {
                Id = query.Id,
                HostId = query.HostId,
                Query = query.Query,
                Data = query.Data,
                Errors = query.Errors,
                HadErrors = query.HadErrors
            }, cancellationToken);
        }
    }
}