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 drillParamsDto = await (from telemetry in db.TelemetryDataSaub
                                        where telemetry.IdTelemetry == idTelemetry &&
                                              telemetry.WellDepth >= startDepth &&
                                              telemetry.WellDepth <= endDepth
                                        group telemetry by telemetry.IdTelemetry into g
                                        select new DrillParamsDto()
                                        {
                                            IdWell = idWell,
                                            DepthStart = startDepth,
                                            DepthEnd = endDepth,
                                            IdWellSectionType = 0,
                                            AxialLoadMin = g.Min(t=> t.AxialLoad) ?? double.NaN, 
                                            AxialLoadAvg = g.Average(t => t.AxialLoad) ?? double.NaN, 
                                            AxialLoadMax = g.Max(t => t.AxialLoad) ?? double.NaN,
                                            PressureMin = g.Min(t=> t.Pressure) ?? double.NaN, 
                                            PressureAvg = g.Average(t => t.Pressure) ?? double.NaN, 
                                            PressureMax = g.Max(t => t.Pressure) ?? double.NaN,
                                            RotorTorqueMin = g.Min(t=> t.RotorTorque) ?? double.NaN, 
                                            RotorTorqueAvg = g.Average(t => t.RotorTorque) ?? double.NaN, 
                                            RotorTorqueMax = g.Max(t => t.RotorTorque) ?? double.NaN,
                                            RotorSpeedMin = g.Min(t=> t.RotorSpeed) ?? double.NaN, 
                                            RotorSpeedAvg = g.Average(t => t.RotorSpeed) ?? double.NaN, 
                                            RotorSpeedMax = g.Max(t => t.RotorSpeed) ?? double.NaN,
                                            FlowMin = g.Min(t => t.Flow) ?? double.NaN,
                                            FlowAvg = g.Min(t => t.Flow) ?? double.NaN,
                                            FlowMax = g.Min(t => t.Flow) ?? double.NaN
                                        })
                                        .AsNoTracking()
                                        .DefaultIfEmpty()
                                        .OrderBy(t => t.AxialLoadMin)
                                        .FirstOrDefaultAsync(token)
                                        .ConfigureAwait(false);

            return drillParamsDto;
        }
        
        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)
                                         .AsNoTracking()
                                         .ToListAsync(token)
                                         .ConfigureAwait(false);
            
            var dto = entities.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)
                       .AsNoTracking()
                       .ToListAsync(token)
                       .ConfigureAwait(false);
            
            var compositeDrillParamsDtos = compositeWellDrillParams.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).ConfigureAwait(false);

            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).ConfigureAwait(false);

            return result;
        }

        public async Task<int> SaveAsync(int idWell, IEnumerable<DrillParamsDto> dtos,
            CancellationToken token = default)
        {
            db.DrillParams.RemoveRange(db.DrillParams.Where(d => d.IdWell == idWell));

            foreach (var dto in dtos)
                dto.IdWell = idWell;

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

            return result;
        }

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

            var result = await base.UpdateAsync(dtoId, dto, token).ConfigureAwait(false);
            return result;
        }
    }
}