using AsbCloudApp.Data;
using AsbCloudApp.Data.DailyReport;
using AsbCloudApp.Data.User;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudDb.Model.DailyReport;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.DailyReport
{

    public class DailyReportService : IDailyReportService
    {
        private readonly IAsbCloudDbContext db;
        private readonly IUserRepository userRepository;
        private readonly IWellOperationRepository wellOperationRepository;
        private readonly IWellService wellService;
        private readonly DailyReportMakerExcel dailyReportMaker = new DailyReportMakerExcel();

        public DailyReportService(
            IAsbCloudDbContext db,
            IWellService wellService,
            IUserRepository userRepository,
            IWellOperationRepository wellOperationRepository)
        {
            this.db = db;
            this.wellService = wellService;
            this.userRepository = userRepository;
            this.wellOperationRepository = wellOperationRepository;

        }

        public async Task<IEnumerable<DailyReportDto>> GetListAsync(int idWell, DateOnly? begin, DateOnly? end, CancellationToken token)
        {
            var well = wellService.GetOrDefault(idWell)
                ?? throw new ArgumentInvalidException(nameof(idWell), "idWell doesn`t exist");

            var query = db.DailyReports.Where(r => r.IdWell == idWell);

            if (begin is not null)
            {
                query = query.Where(d => d.StartDate >= begin);
            }

            if (end is not null)
            {
                query = query.Where(d => d.StartDate <= end);
            }

            var entities = await query.OrderByDescending(e => e.StartDate)
                .AsNoTracking()
                .ToArrayAsync(token)
                .ConfigureAwait(false);

            var factOperationsForDtos = await GetFactOperationsForDailyReportAsync(idWell, token);
            var userDtos = await userRepository.GetAllAsync(token);

            var dtos = entities.Select(entity => Convert(entity, factOperationsForDtos, userDtos));
            return dtos;
        }

        /// <summary>
        /// Получение фактических операций для суточного рапорта
        /// </summary>
        /// <param name="idWell"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        private async Task<IEnumerable<WellOperationDto>> GetFactOperationsForDailyReportAsync(int idWell, CancellationToken token)
        {
            var request = new WellOperationRequest()
            {
                IdWell = idWell,
                OperationType = WellOperation.IdOperationTypeFact,
            };

            var factOperations = await wellOperationRepository.GetAsync(request, token);
            return factOperations;
        }

        public async Task<int> AddAsync(int idWell, DateOnly startDate, int idUser, CancellationToken token)
        {
            var well = wellService.GetOrDefault(idWell)
                ?? throw new ArgumentInvalidException(nameof(idWell), "idWell doesn`t exist");

            var hasEntity = await db.DailyReports
                .AnyAsync(r => r.IdWell == idWell && r.StartDate == startDate, token);
            if (hasEntity)
                throw new ArgumentInvalidException(nameof(startDate), $"daily report on {startDate} already exists");

            var entity = new AsbCloudDb.Model.DailyReport.DailyReport
            {
                IdWell = idWell,
                StartDate = startDate,
                Info = new DailyReportInfo()
                {
                    Head = CreateHeadDailyReportBlock(well, startDate, idUser)
                }
            };
            db.DailyReports.Add(entity);
            var result = await db.SaveChangesAsync(token);
            return result;
        }

        public async Task<int> UpdateBlockAsync(int idWell, DateOnly startDate, ItemInfoDto dto, CancellationToken token)
        {
            var well = wellService.GetOrDefault(idWell)
                ?? throw new ArgumentInvalidException(nameof(idWell), "idWell doesn`t exist");

            var entity = await db.DailyReports.FirstOrDefaultAsync(r => r.IdWell == idWell && r.StartDate == startDate, token)
                ?? throw new ArgumentInvalidException(nameof(startDate), "Daily report doesn`t exist");

            dto.LastUpdateDate = DateTimeOffset.Now;
            if (dto is HeadDto headDto)
                entity.Info.Head = headDto.Adapt<Head>();
            if (dto is BhaDto bhaDto)
                entity.Info.Bha = bhaDto.Adapt<Bha>();
            if (dto is NoDrillingDto noDrillingDto)
                entity.Info.NoDrilling = noDrillingDto.Adapt<NoDrilling>();
            if (dto is SaubDto saubDto)
                entity.Info.Saub = saubDto.Adapt<Saub>();
            if (dto is SignDto signDto)
                entity.Info.Sign = signDto.Adapt<Sign>();

            db.DailyReports.Update(entity);
            var result = await db.SaveChangesAsync(token);
            return result;
        }

        public async Task<Stream?> MakeReportAsync(int idWell, DateOnly date, CancellationToken token)
        {
            var stageIds = WellOperationCategory.WorkStages.Select(w => w.Id).ToArray();
            var wellOperationCategories = wellOperationRepository.GetCategories(true)
                .Where(o => o.IdParent is not null)
                .Where(o => stageIds.Contains(o.IdParent!.Value));               

            var dailyReportDto = await GetOrDefaultAsync(idWell, date, token);
            if (dailyReportDto is null)
                return null;

            var memoryStream = dailyReportMaker.MakeReportFromBlocks(dailyReportDto, wellOperationCategories);

            return memoryStream;
        }

        private async Task<DailyReportDto?> GetOrDefaultAsync(int idWell, DateOnly date, CancellationToken token)
        {
            var entity = await db.DailyReports
                .FirstOrDefaultAsync(r => r.IdWell == idWell && r.StartDate == date, token)
                ?? throw new ArgumentInvalidException(nameof(date), "Daily report doesn`t exist");

            var factOperationsForDtos = await GetFactOperationsForDailyReportAsync(idWell, token);
            var userDtos = await userRepository.GetAllAsync(token);
            var dto = Convert(entity, factOperationsForDtos, userDtos);
            return dto;
        }

        /// <summary>
        /// конвертация данных из модели базы данных в dto
        /// </summary>
        /// <param name="entity">модель базы данных</param>
        /// <param name="factOperationsForDtos">список фактичских операций для формирования суточного рапорта</param>
        /// <param name="users">список пользователей для нахождения последнего изменившего запись</param>
        /// <returns></returns>
        private DailyReportDto Convert(
            AsbCloudDb.Model.DailyReport.DailyReport entity, 
            IEnumerable<WellOperationDto> factOperationsForDtos,
            IEnumerable<UserExtendedDto> users)
        {
            var dto = entity.Info.Adapt<DailyReportDto>();
            dto.StartDate = entity.StartDate;

            var dailyFactOperations = factOperationsForDtos
                .Where(o => DateOnly.FromDateTime(o.DateStart) == dto.StartDate)
                .Where(o => o.IdParentCategory is not null);

            var lastDailyFactOperation = dailyFactOperations
                .OrderByDescending(o => o.LastUpdateDate)
                .FirstOrDefault();
            dto.TimeBalance.IdUser = lastDailyFactOperation?.IdUser;
            dto.TimeBalance.LastUpdateDate = lastDailyFactOperation?.LastUpdateDate;
            dto.TimeBalance.OperationsStat = dailyFactOperations
                .GroupBy(o => o.IdParentCategory!.Value)
                .ToDictionary(g => g.Key, g => g.Sum(o => o.DurationHours));

            var blocks = new ItemInfoDto[] {
                dto.Head,
                dto.Bha,
                dto.NoDrilling,
                dto.TimeBalance,
                dto.Saub,
                dto.Sign
            };

            foreach (var block in blocks)
            {
                if (block.IdUser is not null)
                {
                    block.UserName = users.FirstOrDefault(u => u.Id == block.IdUser.Value)?.MakeDisplayName() 
                        ?? $"userId:{block.IdUser.Value}";
                }
            }

            return dto;
        }

        /// <summary>
        /// Создание блока "Заголовок" по умолчанию
        /// </summary>
        /// <param name="well"></param>
        /// <param name="startDate"></param>
        /// <param name="idUser"></param>
        /// <returns></returns>
        private Head CreateHeadDailyReportBlock(WellDto well, DateOnly startDate, int idUser)
        {
            var customer = well.Companies.FirstOrDefault(company => company.IdCompanyType == 1);
            var contractor = well.Companies.FirstOrDefault(company => company.IdCompanyType == 2);

            return new Head()
            {
                ReportDate = startDate,
                WellName = well.Caption,
                ClusterName = well?.Cluster ?? string.Empty,
                Customer = customer?.Caption ?? string.Empty,
                Contractor = contractor?.Caption ?? string.Empty,
                IdUser = idUser,
                LastUpdateDate = DateTimeOffset.Now,
            };
        }
    }

}