using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services
{
#nullable enable
    /// <summary>
    /// Сервис "Дело скважины"
    /// </summary>
    public class WellFinalDocumentsService : IWellFinalDocumentsService
    {
        private readonly IAsbCloudDbContext context;
        private readonly FileService fileService;
        private readonly IUserService userService;
        private readonly IWellService wellService;
        private readonly IConfiguration configuration;
        private readonly IEmailService emailService;
        private readonly IFileCategoryService fileCategoryService;

        private const int FileServiceThrewException = -1;

        public WellFinalDocumentsService(IAsbCloudDbContext context,
            FileService fileService,
            IUserService userService,
            IWellService wellService,
            IConfiguration configuration,
            IEmailService emailService,
            IFileCategoryService fileCategoryService)
        {
            this.context = context;
            this.fileService = fileService;
            this.userService = userService;
            this.wellService = wellService;
            this.configuration = configuration;
            this.emailService = emailService;
            this.fileCategoryService = fileCategoryService;
        }

        public async Task<int> UpdateRangeAsync(int idWell, IEnumerable<WellFinalDocumentInputDto>? dtos, CancellationToken token)
        {
            if (dtos is not null)
            {
                var entities = dtos
                    .Where(dto => dto.IdsPublishers?.Any() == true)
                    .SelectMany(dto => dto.IdsPublishers
                        .Select(idUser => new WellFinalDocument
                        {
                            IdCategory = dto.IdCategory,
                            IdWell = idWell,
                            IdUser = idUser
                        }));

                var itemsToDelete = context.WellFinalDocuments.Where(d => d.IdWell == idWell);
                context.WellFinalDocuments.RemoveRange(itemsToDelete);

                await context.WellFinalDocuments.AddRangeAsync(entities).ConfigureAwait(false);
                var data = await context.SaveChangesAsync(token).ConfigureAwait(false);

                if (data > 0)
                {
                    var message = "от Вас ожидается загрузка на портал документа «{0}»";
                    await GenerateMessageAsync(entities.Select(x => Convert(x)), message, token);
                }

                return data;
            }
            throw new ArgumentInvalidException("Данные по категориям отсутствуют.");
        }

        public async Task<WellCaseDto> GetByWellId(int idWell, int idUser, CancellationToken token)
        {
            var entities = await context.WellFinalDocuments
                .Include(d => d.Category)
                .Include(d => d.User)
                .Where(d => d.IdWell == idWell)
                .AsNoTracking()
                .ToArrayAsync(token)
                .ConfigureAwait(false);

            var entitiesGroups = entities
                .GroupBy(d => d.IdCategory);

            var categoriesIds = entitiesGroups
                .Select(g => g.Key);

            var files = (await fileService
                .GetInfosAsync(new FileRequest { IdWell = idWell}, token)
                .ConfigureAwait(false))
                .Where(f => categoriesIds.Contains(f.IdCategory))
                .ToArray();

            var docs = entitiesGroups.Select((g) => new WellFinalDocumentDto
            {
                IdCategory = g.Key,
                FilesCount = files
                    .Where(f => f.IdCategory == g.Key)
                    .Count(),
                File = files
                    .Where(f => f.IdCategory == g.Key)
                    .OrderBy(f => f.UploadDate)
                    .LastOrDefault(),
                NameCategory = g.First().Category.Name,
                Publishers = g.Select(i => i.User.Adapt<UserDto>()),
                PermissionToUpload = g.Any(i => i.IdUser == idUser),
            });

            var result = new WellCaseDto
            {
                IdWell = idWell,
                PermissionToSetPubliher = userService.HasPermission(idUser, "WellFinalDocument.editPublisher"),
                WellFinalDocuments = docs,
            };
            return result;
        }

        public async Task<IEnumerable<UserDto>> GetAvailableUsersAsync(int idWell, CancellationToken token)
        {
            var companyIds = await context.RelationCompaniesWells
                .Where(x => x.IdWell == idWell).Select(x => x.IdCompany)
                .ToListAsync(token)
                .ConfigureAwait(false);

            var allUsers = await userService.GetAllAsync(token)
                .ConfigureAwait(false);

            return allUsers.Where(x => {
                    var idCompany = x.IdCompany ?? default(int);
                    return companyIds.Contains(idCompany);
                })
                .OrderBy(x => x.Surname)
                .ToArray();
        }

        public async Task<int> SaveCategoryFile(int idWell, int idCategory, int idUser, Stream fileStream, string fileName, CancellationToken token)
        {
            var entity = await context.WellFinalDocuments
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.IdWell == idWell && x.IdCategory == idCategory && x.IdUser == idUser);

            if (entity is null)
                throw new ArgumentInvalidException("Пользователь не является ответственным за загрузку файла для данной категории.");

            var dto = Convert(entity);

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

            return file?.Id ?? FileServiceThrewException; //TODO: изменить когда файловый сервис будет переведен на nullable
        }

        public async Task<WellFinalDocumentsHistoryDto> GetFilesHistoryByIdCategory(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,
                File = files
            };
        }

        private async Task GenerateMessageAsync(IEnumerable<WellFinalDocumentDBDto> dtos, string message, CancellationToken token)
        {
            foreach (var item in dtos)
            {
                var user = await userService.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);

                    SendMessage(well, user, category.Name, message, token);
                }
            }
        }

        private void SendMessage(WellDto? well, UserDto user, string documentCategory, string message, CancellationToken token)
        {
            var factory = new WellFinalDocumentMailBodyFactory(configuration);
            var subject = factory.MakeSubject(well, documentCategory);
            var body = factory.MakeMailBodyForWellFinalDocument(well, user.Name ?? user.Surname, string.Format(message, documentCategory));
            emailService.EnqueueSend(user.Email, subject, body);
        }

        private static WellFinalDocumentDBDto Convert(WellFinalDocument entity)
            => entity.Adapt<WellFinalDocumentDBDto>();
    }
#nullable disable
}