using AsbCloudApp.Data;
using AsbCloudApp.Data.User;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
using AsbCloudDb.Model;

namespace AsbCloudInfrastructure.Services
{

    /// <summary>
    /// Сервис "Дело скважины"
    /// </summary>
    public class WellFinalDocumentsService : IWellFinalDocumentsService
    {
        private readonly FileService fileService;
        private readonly IUserRepository userRepository;
        private readonly IWellService wellService;
        private readonly IConfiguration configuration;
        private readonly IFileCategoryService fileCategoryService;
        private readonly IWellFinalDocumentsRepository wellFinalDocumentsRepository;
        private readonly NotificationService notificationService;

        public WellFinalDocumentsService(FileService fileService,
            IUserRepository userRepository,
            IWellService wellService,
            IConfiguration configuration,
            IFileCategoryService fileCategoryService,
            IWellFinalDocumentsRepository wellFinalDocumentsRepository,
            NotificationService notificationService)
        {
            this.fileService = fileService;
            this.userRepository = userRepository;
            this.wellService = wellService;
            this.configuration = configuration;
            this.fileCategoryService = fileCategoryService;
            this.wellFinalDocumentsRepository = wellFinalDocumentsRepository;
            this.notificationService = notificationService;
        }

        ///<inheritdoc/>
        public async Task<int> UpdateRangeAsync(int idWell, IEnumerable<WellFinalDocumentInputDto> dtos, CancellationToken token)
        {
            var data = await wellFinalDocumentsRepository.UpdateRangeAsync(idWell, dtos, token);

            var message = "от Вас ожидается загрузка на портал документа «{0}»";
            await NotifyUsersAsync(data, message, token);

            return data.Count();
        }

        ///<inheritdoc/>
        public async Task<int> SaveCategoryFileAsync(int idWell, int idCategory, int idUser, Stream fileStream, string fileName, CancellationToken token)
        {
            var dto = await wellFinalDocumentsRepository.GetCategoryAsync(idWell, idCategory, idUser, token)
                .ConfigureAwait(false);

            var file = await fileService.SaveAsync(dto.IdWell, dto.IdUser, dto.IdCategory, fileName,
                 fileStream, token).ConfigureAwait(false);

            return file.Id;
        }

        ///<inheritdoc/>
        public async Task<WellFinalDocumentsHistoryDto> GetFilesHistoryByIdCategoryAsync(int idWell, int idCategory, CancellationToken token)
        {
            var request = new FileRequest
            {
                IdWell = idWell,
                IdCategory = idCategory,
            };
            var files = await fileService.GetInfosAsync(request, token).ConfigureAwait(false);

            return new WellFinalDocumentsHistoryDto { 
                IdWell = idWell,
                IdCategory = idCategory,
                Files = files
            };
        }

        ///<inheritdoc/>
        public async Task<int> ReNotifyPublishersAsync(int idWell, int idUser, int idCategory, CancellationToken token)
        {
            var wellCase = await wellFinalDocumentsRepository.GetByWellIdAsync(idWell, idUser, token);

            if (!wellCase.PermissionToSetPubliher)
                throw new ForbidException("Повторная отправка оповещений Вам не разрешена");

            var requester = await userRepository.GetOrDefaultAsync(idUser, token);
            if (requester is null)
                throw new ForbidException("Не удается вас опознать");

            var docs = wellCase.WellFinalDocuments
                .Where(doc => doc.IdCategory == idCategory)
                .Where(doc => doc.File is null)
                .SelectMany(doc => doc.Publishers
                    .Select(pub => new WellFinalDocumentDBDto { 
                        IdCategory = idCategory,
                        IdUser = pub.Id,
                        IdWell = idWell
                    }));

            if(!docs.Any())
                throw new ArgumentInvalidException(nameof(idCategory), "Нет такой категории, или в нее уже загружен документ");

            var message = requester.MakeDisplayName() + " ожидает от Вас загрузку на портал документа «{{0}}»";
            await NotifyUsersAsync(docs, message, token);

            return docs.Count();
        }

        private async Task NotifyUsersAsync(IEnumerable<WellFinalDocumentDBDto> dtos, string message, CancellationToken token)
        {
            foreach (var item in dtos)
            {
                var user = await userRepository.GetOrDefaultAsync(item.IdUser, token);
                if (user?.Email is not null)
                {
                    var category = await fileCategoryService.GetOrDefaultAsync(item.IdCategory, token);
                    var well = await wellService.GetOrDefaultAsync(item.IdWell, token)
                        ?? throw new ArgumentInvalidException(nameof(item.IdWell), "idWell doesn`t exist");

                    await SendMessageAsync(well, user, category?.Name ?? string.Empty, message,
                        token);
                }
            }
        }

        private async Task SendMessageAsync(WellDto well, UserDto user, string documentCategory, string message, 
            CancellationToken cancellationToken)
        {
            const int idTransportType = 1;
            
            var factory = new WellFinalDocumentMailBodyFactory(configuration);
            var subject = factory.MakeSubject(well, documentCategory);

            var body = factory.MakeMailBodyForWellFinalDocument(
                well,
                (user.Name ?? user.Surname ?? string.Empty),
                string.Format(message, documentCategory)
            );

            await notificationService.NotifyAsync(new NotifyRequest
            {
                IdUser = user.Id,
                IdNotificationCategory = NotificationCategory.IdSystemNotificationCategory,
                Title = subject,
                Message = body,
                IdTransportType = idTransportType
            }, cancellationToken);
        }
    }

}