using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using Mapster;

namespace AsbCloudInfrastructure.Services
{
    public class DrillParamsService : CrudServiceBase<DrillParamsDto, DrillParams>, IDrillParamsService
    {
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryService telemetryService;
        
        public DrillParamsService(IAsbCloudDbContext context, ITelemetryService telemetryService) 
            : base(context)
        {
            this.db = context;
            this.telemetryService = telemetryService;
        }
        
        public async Task<DrillParamsDto> GetDefaultDrillParamsAsync(int idWell, 
            double startDepth, double endDepth, CancellationToken token = default)
        {
            var idTelemetry = telemetryService.GetIdTelemetryByIdWell(idWell);

            if (idTelemetry is null)
                return null;

            var axialLoads = await GetTelemetryGroupQuery(idWell, startDepth, endDepth)
                                             .Select(g => new
                                             {
                                                 Min = g.Min(t=> t.AxialLoad), 
                                                 Avg = g.Average(t => t.AxialLoad), 
                                                 Max = g.Max(t => t.AxialLoad)
                                             })
                                             .AsNoTracking()
                                             .DefaultIfEmpty()
                                             .OrderBy(t => t.Min)
                                             .FirstOrDefaultAsync(token)
                                             .ConfigureAwait(false);
            
            var pressures = await GetTelemetryGroupQuery(idWell, startDepth, endDepth)
                                            .Select(g => new
                                            {
                                                Min = g.Min(t=> t.Pressure), 
                                                Avg = g.Average(t => t.Pressure), 
                                                Max = g.Max(t => t.Pressure)
                                            })
                                            .AsNoTracking()
                                            .DefaultIfEmpty()
                                            .OrderBy(t => t.Min)
                                            .FirstOrDefaultAsync(token)
                                            .ConfigureAwait(false);
            
            var rotorTorques = await GetTelemetryGroupQuery(idWell, startDepth, endDepth)
                                               .Select(g => new
                                               {
                                                   Min = g.Min(t=> t.RotorTorque), 
                                                   Avg = g.Average(t => t.RotorTorque), 
                                                   Max = g.Max(t => t.RotorTorque)
                                               })
                                               .AsNoTracking()
                                               .DefaultIfEmpty()
                                               .OrderBy(t => t.Min)
                                               .FirstOrDefaultAsync(token)
                                               .ConfigureAwait(false);
            
            var rotorSpeeds = await GetTelemetryGroupQuery(idWell, startDepth, endDepth)
                                              .Select(g => new
                                              {
                                                  Min = g.Min(t=> t.RotorSpeed), 
                                                  Avg = g.Average(t => t.RotorSpeed), 
                                                  Max = g.Max(t => t.RotorSpeed)
                                              })
                                              .AsNoTracking()
                                              .DefaultIfEmpty()
                                              .OrderBy(t => t.Min)
                                              .FirstOrDefaultAsync(token)
                                              .ConfigureAwait(false);
                                            
            var flows = await GetTelemetryGroupQuery(idWell, startDepth, endDepth)
                                        .Select(g => new
                                        {
                                            Min = g.Min(t=> t.Flow), 
                                            Avg = g.Average(t => t.Flow), 
                                            Max = g.Max(t => t.Flow)
                                        })
                                        .AsNoTracking()
                                        .DefaultIfEmpty()
                                        .OrderBy(t => t.Min)
                                        .FirstOrDefaultAsync(token)
                                        .ConfigureAwait(false);

            return new DrillParamsDto()
            {
                IdWell = idWell,
                DepthStart = startDepth,
                DepthEnd = endDepth,
                IdWellSectionType = 0,
                AxialLoadMin = axialLoads.Min ?? 0,
                AxialLoadAvg = axialLoads.Avg ?? 0,
                AxialLoadMax = axialLoads.Max ?? 0,
                PressureMin = pressures.Min ?? 0,
                PressureAvg = pressures.Avg ?? 0,
                PressureMax = pressures.Max ?? 0,
                RotorTorqueMin = rotorTorques.Min ?? 0,
                RotorTorqueAvg = rotorTorques.Avg ?? 0,
                RotorTorqueMax = rotorTorques.Max ?? 0,
                RotorSpeedMin = rotorSpeeds.Min ?? 0,
                RotorSpeedAvg = rotorSpeeds.Avg ?? 0,
                RotorSpeedMax = rotorSpeeds.Max ?? 0,
                FlowMin = flows.Min ?? 0,
                FlowAvg = flows.Avg ?? 0,
                FlowMax = flows.Max ?? 0,
            };
        }
        
        public async Task<IEnumerable<DrillParamsDto>> GetAllAsync(int idWell,
            CancellationToken token = default)
        {
            var entities = await (from p in db.DrillParams
                                         where p.IdWell == idWell
                                         orderby p.Id
                                         select p)
                                         .ToListAsync(token)
                                         .ConfigureAwait(false);
            
            var dto = entities.Select(entity => 
                entity.Adapt<DrillParamsDto>());
            return dto;
        }
        
        public async Task<IEnumerable<DrillParamsDto>> GetCompositeAllAsync(int idWell,
            CancellationToken token = default)
        {
            var compositeWellDrillParams =
                await (from p in db.DrillParams
                       from c in db.WellComposites
                       where c.IdWell == idWell &&  
                             p.IdWell == c.IdWellSrc && 
                             p.IdWellSectionType == c.IdWellSectionType
                       select p)
                       .ToListAsync(token)
                       .ConfigureAwait(false);
            
            var compositeDrillParamsDtos = compositeWellDrillParams.Select(c => c.Adapt<DrillParamsDto>());

            return compositeDrillParamsDtos;
        }
        
        public async Task<int> InsertAsync(int idWell, DrillParamsDto dto,
            CancellationToken token = default)
        {
            dto.IdWell = idWell;

            var result = await base.InsertAsync(dto, token);

            return result;
        }

        public async Task<int> InsertRangeAsync(int idWell, IEnumerable<DrillParamsDto> dtos,
            CancellationToken token = default)
        {
            foreach (var dto in dtos)
                dto.IdWell = idWell;

            var result = await base.InsertRangeAsync(dtos, token);

            return result;
        }
        
        public override async Task<int> UpdateAsync(int idWell, DrillParamsDto dto, 
            CancellationToken token = default)
        {
            dto.IdWell = idWell;

            var result = await base.UpdateAsync(dto.Id, dto, token);
            return result;
        }

        private IQueryable<IGrouping<int, TelemetryDataSaub>> GetTelemetryGroupQuery(int idTelemetry, 
            double startDepth, double endDepth)
        {
            return from telemetry in db.TelemetryDataSaub
                   where telemetry.IdTelemetry == idTelemetry &&
                         telemetry.WellDepth >= startDepth &&
                         telemetry.WellDepth <= endDepth
                   group telemetry by telemetry.IdTelemetry;
        }
    }
}