using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Services.CommunationServices;
using Birdmap.BLL.Services.CommunicationServices.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Birdmap.BLL.Services.CommunicationServices.Mqtt
{
    internal class MqttClientService : CommunicationServiceBase, IMqttClientService
    {
        private readonly IMqttClient _mqttClient;
        private readonly IMqttClientOptions _options;

        public override bool IsConnected => _mqttClient.IsConnected;

        public MqttClientService(IMqttClientOptions options, ILogger<MqttClientService> logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
            : base(logger, inputService, hubContext)
        {
            _options = options;

            _mqttClient = new MqttFactory().CreateMqttClient();
            ConfigureMqttClient();
        }

        private void ConfigureMqttClient()
        {
            _mqttClient.ConnectedHandler = this;
            _mqttClient.DisconnectedHandler = this;
            _mqttClient.ApplicationMessageReceivedHandler = this;
        }

        public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
        {
            var message = eventArgs.ApplicationMessage.ConvertPayloadToString();

            _logger.LogDebug($"Recieved [{eventArgs.ClientId}] " +
                $"Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {message} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel} | Retain: {eventArgs.ApplicationMessage.Retain}");

            return ProcessJsonMessageAsync(message);
        }

        public async Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs)
        {
            try
            {
                var topic = _options.UserProperties.SingleOrDefault(up => up.Name == "Topic")?.Value;
                _logger.LogInformation($"Connected. Auth result: {eventArgs.AuthenticateResult}. Subscribing to topic: {topic}");

                await _mqttClient.SubscribeAsync(topic);
                StartMessageTimer();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Cannot subscribe...");
            }
        }

        public async Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
        {
            _logger.LogDebug(eventArgs.Exception, $"Disconnected. Reason {eventArgs.ReasonCode}. Auth result: {eventArgs.AuthenticateResult}. Reconnecting...");

            await Task.Delay(TimeSpan.FromSeconds(5));

            try
            {
                StopMessageTimer();
                await _mqttClient.ConnectAsync(_options, CancellationToken.None);
            }
            catch (Exception ex)
            {
                _logger.LogDebug(ex, $"Reconnect failed...");
            }
        }

        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                await _mqttClient.ConnectAsync(_options);
                if (!_mqttClient.IsConnected)
                {
                    await _mqttClient.ReconnectAsync();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Cannot connect...");
            }
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            try
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    var disconnectOption = new MqttClientDisconnectOptions
                    {
                        ReasonCode = MqttClientDisconnectReason.NormalDisconnection,
                        ReasonString = "NormalDiconnection"
                    };
                    await _mqttClient.DisconnectAsync(disconnectOption, cancellationToken);
                }
                await _mqttClient.DisconnectAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Cannot disconnect...");
            }
        }
    }
}
