using Insight.Agent.Enums;
using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using static Insight.Agent.Messages.Event;

namespace Insight.Server.Network.Handlers.Agent
{
    public class EventHandler : IAgentMessageHandler<AgentSession>
    {
        private readonly IMongoDatabase _database;
        private readonly ILogger<EventHandler> _logger;

        public EventHandler(IMongoDatabase database, ILogger<EventHandler> logger)
        {
            _database = database;
            _logger = logger;
        }

        public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IAgentMessage
        {
            if (message is Event @event)
            {
                await OnEventAsync(sender, @event, cancellationToken);
            }
        }

        private async ValueTask OnEventAsync(AgentSession session, Event @event, CancellationToken cancellationToken)
        {
            var agentEntity = await _database.Agent().Find(Builders<AgentEntity>.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken);
            if (agentEntity is null) return;

            var hostEntity = await _database.Host().Find(Builders<HostEntity>.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken);
            if (hostEntity is null) return;

            if (FilterEventId(@event)) return;

            var hostLog = await InsertHostLogAsync(hostEntity, @event, cancellationToken);

            if (hostLog is null || FilterMonitoringHostLog(hostLog)) return;

            await _database.HostLogMonitoring()
                    .InsertOneAsync(new HostLogMonitoringEntity
                    {
                        Host = hostEntity.Id,
                        Insert = hostLog.Insert,
                        Timestamp = hostLog.Timestamp,
                        Category = hostLog.Category,
                        Status = hostLog.Status,
                        Message = hostLog.Message,
                        Dispatch = DispatchEnum.Pending.ToString()
                    }, cancellationToken: cancellationToken);
        }

        private async ValueTask<AgentLogEntity?> InsertAgentLogAsync(AgentSession session, Event @event, CancellationToken cancellationToken)
        {
            var agentEntity = await _database.Agent()
                .Aggregate()
                .Match(Builders<AgentEntity>.Filter.Eq(p => p.Id, session.Id))
                .Lookup<AgentEntity, HostEntity, AgentEntity>(_database.Host(), p => p.Serial, p => p.Agent, p => p.Hosts)
                .FirstOrDefaultAsync(cancellationToken);

            if (agentEntity is null) return null;

            StatusEnum? status = @event.Status switch
            {
                StatusType.Information => StatusEnum.Information,
                StatusType.Warning => StatusEnum.Warning,
                StatusType.Error => StatusEnum.Error,
                _ => null
            };

            CategoryEnum? category = @event.Category.ToLower() switch
            {
                "network" => CategoryEnum.Network,
                "application" => CategoryEnum.Application,
                "security" => CategoryEnum.Security,
                "system" => CategoryEnum.System,
                _ => null
            };

            var date = DateTime.Now;

            var log = new AgentLogEntity
            {
                Insert = date,
                Agent = agentEntity.Id,
                Timestamp = @event.Timestamp,
                EventId = @event.EventId.ToString(),
                Source = @event.Source,
                Status = status.ToString(),
                Category = category.ToString(),
                Message = @event.Message
            };

            await _database.AgentLog().InsertOneAsync(log, cancellationToken: cancellationToken);
            return log;
        }

        private async ValueTask<HostLogEntity?> InsertHostLogAsync(HostEntity hostEntity, Event @event, CancellationToken cancellationToken)
        {
            StatusEnum? status = @event.Status switch
            {
                StatusType.Information => StatusEnum.Information,
                StatusType.Warning => StatusEnum.Warning,
                StatusType.Error => StatusEnum.Error,
                StatusType.Critical => StatusEnum.Error,
                StatusType.Unknown => null,
                _ => null
            };

            CategoryEnum? category = null;
            switch (@event.Category)
            {
                case var _ when @event.Category.Contains("network", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Network;
                    break;

                case var _ when @event.Category.Contains("application", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Application;
                    break;

                case var _ when @event.Category.Contains("security", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Security;
                    break;

                case var _ when @event.Category.Contains("system", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.System;
                    break;

                case var _ when @event.Category.Contains("printservice", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Printer;
                    break;

                case var _ when @event.Category.Contains("taskscheduler", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Task;
                    break;

                case var _ when @event.Category.Contains("terminalservices", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.RDP;
                    break;

                case var _ when @event.Category.Contains("smbclient", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Network;
                    break;

                case var _ when @event.Category.Contains("smbserver", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.Network;
                    break;

                case var _ when @event.Category.Contains("storagespaces", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.System;
                    break;

                case var _ when @event.Category.Contains("diagnostics", StringComparison.InvariantCultureIgnoreCase):
                    category = CategoryEnum.System;
                    break;

                default:
                    break;
            }

            var date = DateTime.Now;

            var log = new HostLogEntity
            {
                Insert = date,
                Host = hostEntity.Id,
                Timestamp = @event.Timestamp,
                EventId = @event.EventId.ToString(),
                Status = status.ToString(),
                Source = @event.Source,
                Category = category?.ToString(),
                Message = @event.Message
            };

            await _database.HostLog().InsertOneAsync(log, cancellationToken: cancellationToken);
            return log;
        }

        private bool FilterEventId(Event @event)
        {
            var filter = new List<int>
            {
                0,
                3,
                4,
                16,
                37,
                900,
                902,
                903,
                1001,
                1003,
                1008,
                1023,
                1066,
                4624,
                4625,
                4634,
                4648,
                4672,
                4776,
                4798,
                4799,
                5058,
                5059,
                5061,
                5379,
                5612,
                5781,
                //7036,
                8014,
                8016,
                8019,
                8194,
                8224,
                9009,
                9027,
                10001,
                10016,
                16384,
                16394,
                36874,
                36888,
            };

            if (filter.Any(p => p == @event.EventId)) return true;
            return false;
        }

        private bool FilterMonitoringHostLog(HostLogEntity hostLog)
        {
            //_logger.LogDebug($"try filter event: {hostLog.Category}.{hostLog.Source}.{hostLog.Status}");

            if (Enum.TryParse(hostLog.Status, out StatusType status) is false) return true;

            if (hostLog.Category == CategoryEnum.System.ToString())
            {
                if (hostLog.Source == "Service Control Manager" && status < StatusType.Warning) return true;
            }

            if (hostLog.Category == CategoryEnum.Task.ToString())
            {
                if (status < StatusType.Error) return true;
            }

            // skip rdp infos
            if (hostLog.Category == CategoryEnum.RDP.ToString())
            {
                if (hostLog.EventId == "261") return true;
            }

            // skip smbclient (veeam errors)
            if (hostLog.Category == CategoryEnum.Security.ToString())
            {
                if (hostLog.Source == "Microsoft-Windows-SMBClient") return true;
            }

            return false;
        }
    }
}