using Insight.Agent.Enums;
using Insight.Agent.Interfaces;
using Insight.Agent.Messages;
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Server.Models;
using MongoDB.Driver;
using System.Text;
using System.Text.RegularExpressions;
using static Insight.Server.Models.MonitorMessage;

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

        public TrapHandler(IMongoDatabase database)
        {
            _database = database;
        }

        public async ValueTask HandleAsync<TMessage>(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IAgentMessage
        {
            if (message is Trap trap)
            {
                await OnTrapAsync(sender, trap, cancellationToken);
            }
        }

        private async ValueTask OnTrapAsync(AgentSession session, Trap? trap, CancellationToken cancellationToken)
        {
            if (trap is null) return;

            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 (TryParse(trap, out var monitoring) is false)
            {
                //_logger.LogWarning($"Failed to parse");
            }

            // insert monitoring log
            var monitoringLog = new HostLogMonitoringEntity
            {
                Host = hostEntity.Id,
                Insert = DateTime.Now,
                Timestamp = monitoring?.Timestamp,
                Category = monitoring?.Category?.ToString(),
                Status = monitoring?.Status?.ToString(),
                Hostname = monitoring?.Hostname,
                Task = monitoring?.Subject,
                Message = monitoring?.Message,
                Dispatch = DispatchEnum.Pending.ToString()
            };

            await _database.HostLogMonitoring()
                .InsertOneAsync(monitoringLog, cancellationToken: cancellationToken);

            // insert host log
            var log = new HostLogEntity
            {
                Insert = monitoringLog.Insert,
                Host = monitoringLog.Host,
                Category = monitoringLog.Category,
                Status = monitoringLog.Status,
                Source = "Trap",
                Message = monitoringLog?.Task,
                Timestamp = monitoringLog?.Insert
            };

            if (monitoringLog?.Message is not null) log.Message += $"\n{monitoringLog.Message}";

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

        private static bool TryParse(Trap packet, out MonitorMessage? monitoring)
        {
            monitoring = null;

            if (packet is null || packet.Data is null || packet.Data.Any() is false) return false;

            monitoring = new MonitorMessage
            {
                Community = packet.Community,
                Category = CategoryEnum.Monitoring,
                Endpoint = packet.Endpoint,
                Timestamp = packet.Timestamp
            };

            if (Enum.TryParse<ApplicationEnum>(packet.Community, true, out var application))
            {
                monitoring.Application = application;
            }

            StatusEnum? status;
            string? task;
            string? message;

            switch (application)
            {
                case ApplicationEnum.Acronis:
                    monitoring.Application = ApplicationEnum.Acronis;
                    if (ParseAcronis(packet.Data, out status, out task, out message) is false) return false;
                    break;
                case ApplicationEnum.Veeam:
                    monitoring.Application = ApplicationEnum.Veeam;
                    if (ParseVeeam(packet.Data, out status, out task, out message) is false) return false;
                    break;
                case ApplicationEnum.QNAP:
                    monitoring.Application = ApplicationEnum.QNAP;
                    monitoring.Category = CategoryEnum.System;
                    monitoring.Hostname = packet.Hostname;
                    if (ParseQnap(packet.Data, out status, out task, out message) is false) return false;
                    break;
                default:
                    return false;
            }

            monitoring.Status = status;
            monitoring.Subject = task;
            monitoring.Message = message;

            return true;
        }

        private static bool ParseAcronis(List<KeyValuePair<string, string>> data, out StatusEnum? status, out string? task, out string? message)
        {
            status = data[0].Value.ToLower() switch
            {
                "erfolgreich" => StatusEnum.Information,
                "success" => StatusEnum.Information,
                "information" => StatusEnum.Information,
                "warnung" => StatusEnum.Warning,
                "warning" => StatusEnum.Warning,
                "fehler" => StatusEnum.Error,
                "failed" => StatusEnum.Error,
                "error" => StatusEnum.Error,
                _ => null,
            };

            task = null;
            message = null;

            var parsed = false;

            try
            {
                var trim = data[1].Value.Split(new string[] { ":" }, StringSplitOptions.None);
                task = trim[1].Split(new string[] { "'" }, StringSplitOptions.None)[1].Split("'")[0].Trim();
                message = trim[1].Split(new string[] { "' " }, StringSplitOptions.None)[1].Trim();

                parsed = true;
            }
            catch (Exception)
            {
                // skipped for base64 parse
            }

            if (parsed) return true;

            try
            {
                var content = Regex.Replace(data[1].Value, @"\s+", "");
                var bytes = Enumerable.Range(0, content.Length)
                                 .Where(x => x % 2 == 0)
                                 .Select(x => Convert.ToByte(content.Substring(x, 2), 16))
                                 .ToArray();

                content = Encoding.UTF8.GetString(bytes);

                var trim = content.Split(new string[] { ":" }, StringSplitOptions.None);
                task = trim[1].Split(new string[] { "'" }, StringSplitOptions.None)[1].Split("'")[0].Trim();
                message = trim[1].Split(new string[] { "' " }, StringSplitOptions.None)[1].Trim();

                parsed = true;
            }
            catch (Exception ex)
            {
                //_logger.LogError("{ex}", ex);
            }

            if (parsed) return true;
            return false;
        }

        private static bool ParseVeeam(List<KeyValuePair<string, string>> data, out StatusEnum? status, out string? task, out string? message)
        {
            status = null;
            task = null;
            message = null;

            var parsed = false;

            try
            {
                var summary = false;

                if (Guid.TryParse(data[0].Value, out _))
                    summary = true;

                if (data[1].Value.ToLower() == "backup configuration job")
                    return false;

                status = (summary ? data[2].Value.ToLower() : data[3].Value.ToLower()) switch
                {
                    "success" => StatusEnum.Information,
                    "warning" => StatusEnum.Warning,
                    "failed" => StatusEnum.Error,
                    "error" => StatusEnum.Error,
                    _ => null,
                };

                task = data[1].Value;
                parsed = true;
            }
            catch (Exception)
            {

            }

            if (parsed) return true;
            return false;
        }

        private static bool ParseQnap(List<KeyValuePair<string, string>> data, out StatusEnum? status, out string? task, out string? message)
        {
            status = StatusEnum.Information;
            task = null;
            message = string.Empty;

            var parsed = false;

            try
            {
                var keywords = new Dictionary<string, StatusEnum>
                {
                    { "power", StatusEnum.Information },
                    { "rights", StatusEnum.Information },
                    { "added", StatusEnum.Information },
                    { "changed", StatusEnum.Information },
                    { "password", StatusEnum.Information },
                    { "firmware", StatusEnum.Information },
                    { "restarting", StatusEnum.Information },
                    { "detected", StatusEnum.Warning },
                    { "external", StatusEnum.Warning },
                    { "threshold", StatusEnum.Warning },
                    { "file system", StatusEnum.Warning },
                    { "raid", StatusEnum.Warning },
                    { "full", StatusEnum.Error },
                    { "failure", StatusEnum.Error },
                    { "failed", StatusEnum.Error },
                    { "resyncing", StatusEnum.Error },
                    { "degraded", StatusEnum.Error },
                    { "error", StatusEnum.Error },
                    { "without error", StatusEnum.Information },
                    { "ncsi", StatusEnum.Information }
                };

                foreach (var key in keywords)
                {
                    if (Regex.IsMatch(string.Concat(data).ToLowerInvariant(), $@"\b{key.Key}\b"))
                    {
                        status = key.Value;
                    }
                }

                foreach (var kv in data)
                {
                    message += kv.Value;
                }

                parsed = true;
            }
            catch (Exception ex)
            {
                //_logger.LogError("{ex}", ex);
            }

            if (parsed) return true;
            return false;
        }
    }
}