using Insight.Agent.Enums;
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace Insight.Server.Services
{
    internal class DispatchService : BackgroundService
    {
        private readonly HttpClient _httpClient;
        private readonly IMongoDatabase _database;
        private readonly IConfiguration _configuration;
        private readonly ILogger<DispatchService> _logger;

        public DispatchService(HttpClient httpClient, IMongoDatabase database, IConfiguration configuration, ILogger<DispatchService> logger)
        {
            _httpClient = httpClient;
            _database = database;
            _configuration = configuration;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            _logger.LogTrace("ExecuteAsync");

            var enabled = _configuration.GetValue<bool?>(Appsettings.DispatchWebmatic) ?? throw new Exception($"{Appsettings.DispatchWebmatic} value not set (appsettings)");
            if (enabled is false) return;

            try
            {
                while (cancellationToken.IsCancellationRequested is false)
                {
                    await DispatchAsync(cancellationToken);
                    await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                }
            }
            catch (OperationCanceledException) { }
            catch (Exception) { }
        }

        private async ValueTask DispatchAsync(CancellationToken cancellationToken)
        {
            _logger.LogTrace($"DispatchAsync");

            var pendings = await _database.HostLogMonitoring()
                .Find(Builders<HostLogMonitoringEntity>
                .Filter.Eq(p => p.Dispatch, DispatchEnum.Pending.ToString()))
                .Limit(10)
                .ToListAsync(cancellationToken);

            if (pendings is null || pendings.Any() is false) return;

            foreach (var entity in pendings)
            {
                try
                {
                    var result = await SendAsync(entity, default);

                    await _database.HostLogMonitoring()
                        .UpdateOneAsync(Builders<HostLogMonitoringEntity>.Filter
                        .Eq(p => p.Id, entity.Id), Builders<HostLogMonitoringEntity>
                        .Update
                        .Set(p => p.Dispatch, result.ToString()), cancellationToken: default);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex.Message);
                    _logger.LogTrace(ex.StackTrace);
                }
                finally
                {
                    // webmatic safety offset
                    await Task.Delay(TimeSpan.FromSeconds(1), default);
                }
            }
        }

        private async ValueTask<DispatchEnum> SendAsync(HostLogMonitoringEntity monitoring, CancellationToken cancellationToken)
        {
            _logger.LogTrace($"SendAsync ({monitoring})");

            var monitoringApi = Monitoring.LogUri;
            var monitoringContent = new List<KeyValuePair<string, string>>();
            var monitoringResult = new List<KeyValuePair<string, string>>();

            // adjust by category
            if (Enum.TryParse<CategoryEnum>(monitoring.Category, true, out var monitoringCategory) is false) return DispatchEnum.Failure;

            if (monitoringCategory == CategoryEnum.Monitoring) monitoringApi = Monitoring.StatusUri;

            // set category (if log)
            if (monitoringApi == Monitoring.LogUri) monitoringContent.Add(new KeyValuePair<string, string>("category", monitoringCategory.ToString()));

            // host resolve
            var hostEntity = await _database.Host().Find(Builders<HostEntity>.Filter.Eq(p => p.Id, monitoring.Host?.ToString())).FirstOrDefaultAsync(cancellationToken);
            if (hostEntity is null) return DispatchEnum.Failure;

            // customer resolve
            var customerEntity = await _database.Customer().Find(Builders<CustomerEntity>.Filter.Eq(p => p.Id, hostEntity.Customer)).FirstOrDefaultAsync(cancellationToken);
            if (hostEntity is null) return DispatchEnum.Failure;

            // set host name if no remote host set
            if (string.IsNullOrEmpty(monitoring.Hostname)) monitoring.Hostname = hostEntity.Name;

            // remove any domain from hostname
            if (monitoring.Hostname is not null && monitoring.Hostname.Contains('.')) monitoring.Hostname = monitoring.Hostname.Split(".")[0];

            // add customer tag to hostname
            monitoring.Hostname += $".{customerEntity.Tag}";

            // if task null, set hostname 
            if (string.IsNullOrEmpty(monitoring.Task)) monitoring.Task = monitoring.Hostname;

            // insert hostname as computer-name (lowercase)
            monitoringContent.Add(new KeyValuePair<string, string>("computer_name", monitoring.Hostname.ToLower()));

            // insert converted status (api styled)
            if (Enum.TryParse<StatusEnum>(monitoring.Status, true, out var monitoringStatus) is false) return DispatchEnum.Failure;

            monitoringContent.Add(monitoringStatus switch
            {
                StatusEnum.Information => new KeyValuePair<string, string>("status", monitoringApi == Monitoring.StatusUri ? "erfolgreich" : "info"),
                StatusEnum.Warning => new KeyValuePair<string, string>("status", monitoringApi == Monitoring.StatusUri ? "Interaktion" : "warning"),
                StatusEnum.Error => new KeyValuePair<string, string>("status", monitoringApi == Monitoring.StatusUri ? "fehlgeschlagen" : "error"),
                _ => throw new NotImplementedException(nameof(monitoringStatus))
            });

            // insert task, timestamp, message, 
            monitoringContent.Add(new KeyValuePair<string, string>("task", monitoring.Task));

            if (monitoring.Timestamp is not null)
            {
                monitoringContent.Add(new KeyValuePair<string, string>("timestamp", monitoring.Timestamp.Value.ToLocalTime().ToString()));
            }

            if (string.IsNullOrWhiteSpace(monitoring.Message) is false)
            {
                monitoringContent.Add(new KeyValuePair<string, string>("message", monitoring.Message));
            }

            // send message
            var result = await _httpClient.PostAsync(monitoringApi, new FormUrlEncodedContent(monitoringContent), default);

            monitoringResult.Add(new KeyValuePair<string, string>("HttpStatusCode", result.StatusCode.ToString()));
            monitoringResult.Add(new KeyValuePair<string, string>("HttpResponseMessage", await result.Content.ReadAsStringAsync(default)));

            // if content != "OK"
            if (result is null || result.IsSuccessStatusCode == false) return DispatchEnum.Failure;

            // success
            return DispatchEnum.Success;
        }
    }
}