using Birdmap.BLL.Interfaces;
using Birdmap.BLL.Options;
using Birdmap.BLL.Services;
using Birdmap.BLL.Services.CommunationServices.RabbitMq;
using Birdmap.BLL.Services.CommunicationServices;
using Birdmap.BLL.Services.CommunicationServices.Mqtt;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;

namespace Birdmap.BLL
{
    public static class Startup
    {
        public static IServiceCollection ConfigureBLL(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddTransient<IAuthService, AuthService>();
            services.AddTransient<IUserService, UserService>();
            services.AddTransient<IServiceService, ServiceService>();

            if (configuration.GetValue<bool>("UseDummyServices"))
            {
                services.AddTransient<IInputService, DummyDeviceAndInputService>();
                services.AddTransient<IDeviceService, DummyDeviceAndInputService>();
            }
            else
            {
                var baseUrl = configuration.GetValue<string>("ServicesBaseUrl");

                services.AddTransient<IInputService, LiveInputService>(serviceProvider =>
                {
                    var httpClient = serviceProvider.GetService<HttpClient>();
                    var service = new LiveInputService(baseUrl, httpClient);
                    return service;
                });
                services.AddTransient<IDeviceService, LiveDeviceService>(serviceProvider =>
                {
                    var httpClient = serviceProvider.GetService<HttpClient>();
                    var service = new LiveDeviceService(baseUrl, httpClient);
                    return service;
                });
            }

            services.AddSignalR();

            var mqtt = configuration.GetSection("Mqtt");

            var client = mqtt.GetSection("ClientSettings");
            var clientSettings = new
            {
                Id = client.GetValue<string>("Id"),
                Username = client.GetValue<string>("Username"),
                Password = client.GetValue<string>("Password"),
                Topic = client.GetValue<string>("Topic"),
            };

            var brokerHost = mqtt.GetSection("BrokerHostSettings");
            var brokerHostSettings = new
            {
                Host = brokerHost.GetValue<string>("Host"),
                Port = brokerHost.GetValue<int>("Port"),
                VirtualHost = brokerHost.GetValue<string>("VirtualHost"),
            };

            var exchange = mqtt.GetSection("ExchangeSettings");
            var exchangeSettings = new
            {
                Name = exchange.GetValue<string>("Name"),
                Type = exchange.GetValue<string>("Type"),
                Durable = exchange.GetValue<bool>("Durable"),
                AutoDelete = exchange.GetValue<bool>("AutoDelete"),
            };

            var queue = mqtt.GetSection("QueueSettings");
            var queueSettings = new
            {
                Name = queue.GetValue<string>("Name"),
                Durable = exchange.GetValue<bool>("Durable"),
                Exclusive = exchange.GetValue<bool>("Exclusive"),
                AutoDelete = exchange.GetValue<bool>("AutoDelete"),
            };

            if (configuration.GetValue<bool>("UseRabbitMq"))
            {
                services.AddRabbitMqClientServiceWithConfig(new RabbitMqClientOptions(
                    Hostname: brokerHostSettings.Host,
                    Port: brokerHostSettings.Port,
                    VirtualHost: brokerHostSettings.VirtualHost,
                    Username: clientSettings.Username,
                    Password: clientSettings.Password,
                    ExchangeName: exchangeSettings.Name,
                    ExchangeType: exchangeSettings.Type,
                    ExchangeDurable: exchangeSettings.Durable,
                    ExchangeAutoDelete: exchangeSettings.AutoDelete,
                    QueueName: queueSettings.Name,
                    QueueDurable: queueSettings.Durable,
                    QueueExclusive: queueSettings.Exclusive,
                    QueueAutoDelete: queueSettings.AutoDelete,
                    Topic: clientSettings.Topic));
            }
            else
            {
                services.AddMqttClientServiceWithConfig(opt =>
                {
                    opt
                    .WithTopic(clientSettings.Topic)
                    .WithCredentials(clientSettings.Username, clientSettings.Password)
                    .WithClientId(clientSettings.Id)
                    .WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port);
                });
            }

            return services;
        }

        private static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services, Action<MqttClientOptions> configureOptions)
        {
            services.AddSingleton(serviceProvider =>
            {
                var optionBuilder = new MqttClientOptions(serviceProvider);
                configureOptions(optionBuilder);
                return optionBuilder.Build();
            });

            services.AddClientServiceWithProvider<MqttClientService>();
            return services;
        }

        private static IServiceCollection AddRabbitMqClientServiceWithConfig(this IServiceCollection services, RabbitMqClientOptions options)
        {
            services.AddSingleton(options);

            services.AddClientServiceWithProvider<RabbitMqClientService>();
            return services;
        }

        private static IServiceCollection AddClientServiceWithProvider<T>(this IServiceCollection services) where T : class, ICommunicationService
        {
            services.AddSingleton<T>();
            services.AddSingleton<IHostedService>(serviceProvider =>
            {
                return serviceProvider.GetService<T>();
            });
            services.AddSingleton<ICommunicationServiceProvider>(serviceProvider =>
            {
                var clientService = serviceProvider.GetService<T>();
                var clientServiceProvider = new CommunicationServiceProvider(clientService);
                return clientServiceProvider;
            });
            return services;
        }
    }
}
