using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AsbCloudInfrastructure.Services.Email
{

    public class EmailNotificationTransportService : INotificationTransportService
    {
        private readonly BackgroundWorker backgroundWorker;
        private readonly bool IsConfigured;
        private readonly string sender;
        private readonly string smtpServer;
        private readonly string smtpPassword;

        public EmailNotificationTransportService(BackgroundWorker backgroundWorker, 
            IConfiguration configuration)
        {
            sender = configuration.GetValue("email:sender", string.Empty);
            smtpPassword = configuration.GetValue("email:password", string.Empty);
            smtpServer = configuration.GetValue("email:smtpServer", string.Empty);

            var configError = string.IsNullOrEmpty(sender) ||
                string.IsNullOrEmpty(smtpPassword) ||
                string.IsNullOrEmpty(smtpServer);

            IsConfigured = !configError;

            this.backgroundWorker = backgroundWorker;
        }

        public int IdTransportType => 1;
        
        public Task SendAsync(NotificationDto notification, CancellationToken cancellationToken)
        {
            if (!IsConfigured)
            {
                Trace.TraceWarning("smtp is not configured");
                return Task.CompletedTask;
            }

            var workId = MakeWorkId(notification.IdUser, notification.Title, notification.Message);
            if (!backgroundWorker.Contains(workId))
            {
                var workAction = MakeEmailSendWorkAction(notification);

                var work = new WorkBase(workId, workAction);
                backgroundWorker.Push(work);
            }

            return Task.CompletedTask;
        }

        public Task SendRangeAsync(IEnumerable<NotificationDto> notifications, CancellationToken cancellationToken)
        {
            var tasks = notifications
                .Select(notification => SendAsync(notification, cancellationToken));

            return Task.WhenAll(tasks);
        }
        
        private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(NotificationDto notification)
        {
            return async (_, serviceProvider, token) =>
            {
                var notificationRepository = serviceProvider.GetRequiredService<INotificationRepository>();
                var userRepository = serviceProvider.GetRequiredService<IUserRepository>();

                var user = await userRepository.GetOrDefaultAsync(notification.IdUser, token)
                    ?? throw new ArgumentInvalidException(nameof(notification.IdUser), "Пользователь не найден");

                if(!MailAddress.TryCreate(user.Email, out var mailAddress))
                {
                    Trace.TraceWarning($"Mail {user.Email} is not correct.");
                    throw new ArgumentInvalidException(nameof(user.Email), $"Mail {user.Email} is not null.");
                }

                var from = new MailAddress(sender);
                var message = new MailMessage
                {
                    From = from
                };
                
                message.To.Add(mailAddress.Address);
                message.BodyEncoding = System.Text.Encoding.UTF8;
                message.Body = notification.Message;
                message.Subject = notification.Title;
                message.IsBodyHtml = true;

                using var client = new SmtpClient(smtpServer);
                client.EnableSsl = true;
                client.UseDefaultCredentials = false;
                client.Credentials = new System.Net.NetworkCredential(sender, smtpPassword);

                await client.SendMailAsync(message, token);

                notification.SentDate = DateTime.UtcNow;

                await notificationRepository.UpdateAsync(notification, token);
                
                Trace.TraceInformation($"Send email to {user.Email} subj:{notification.Title} html body count {notification.Message.Length}");
            };
        }

        private static string MakeWorkId(int idUser, string subject, string content)
        {
            var hash = idUser.GetHashCode();
            hash ^= subject.GetHashCode();
            hash ^= content.GetHashCode();
            return hash.ToString("x");
        }
    }
}