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

namespace AsbCloudInfrastructure.Services
{
#nullable enable
    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.GetOrDefaultIdTelemetryByIdWell(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,
                                            Depth = new MinMaxDto<double>
                                            {
                                                Min = endDepth,
                                                Max = startDepth
                                            },
                                            IdWellSectionType = 0,
                                            AxialLoad = new MinMaxExtendedViewDto
                                            {
                                                Min = g.Min(t => t.AxialLoad) ?? double.NaN,
                                                Avg = g.Average(t => t.AxialLoad) ?? double.NaN,
                                                Max = g.Max(t => t.AxialLoad) ?? double.NaN
                                            },
                                            Pressure = new MinMaxExtendedViewDto
                                            {
                                                Min = g.Min(t => t.Pressure) ?? double.NaN,
                                                Avg = g.Average(t => t.Pressure) ?? double.NaN,
                                                Max = g.Max(t => t.Pressure) ?? double.NaN
                                            },
                                            RotorTorque = new MinMaxExtendedViewDto
                                            {
                                                Min = g.Min(t => t.RotorTorque) ?? double.NaN,
                                                Avg = g.Average(t => t.RotorTorque) ?? double.NaN,
                                                Max = g.Max(t => t.RotorTorque) ?? double.NaN
                                            },
                                            RotorSpeed = new MinMaxExtendedViewDto
                                            {
                                                Min = g.Min(t => t.RotorSpeed) ?? double.NaN,
                                                Avg = g.Average(t => t.RotorSpeed) ?? double.NaN,
                                                Max = g.Max(t => t.RotorSpeed) ?? double.NaN
                                            },
                                            Flow = new MinMaxExtendedViewDto
                                            {
                                                Min = g.Min(t => t.Flow) ?? double.NaN,
                                                Avg = g.Min(t => t.Flow) ?? double.NaN,
                                                Max = g.Min(t => t.Flow) ?? double.NaN
                                            }
                                        })
                                        .AsNoTracking()
                                        .DefaultIfEmpty()
                                        .OrderBy(t => t.AxialLoad.Min)
                                        .FirstOrDefaultAsync(token)
                                        .ConfigureAwait(false);

            return drillParamsDto;
        }

        public async Task<IEnumerable<DrillParamsDto>> GetAllAsync(int idWell,
            CancellationToken token = default)
        {
            var entities = await db.DrillParams
                                .Where(p => p.IdWell == idWell)
                                .OrderBy(p=> p.Id)
                                .AsNoTracking()
                                .ToArrayAsync(token)
                                .ConfigureAwait(false);

            var dtos = entities.Select(p => 
            {
                var dto = new DrillParamsDto
                {
                    IdWell = p.IdWell,
                    Id = p.Id,
                    IdWellSectionType = p.IdWellSectionType,
                    Depth = new MinMaxDto<double> { Max = p.PressureMax, Min = p.PressureMin },
                    Pressure = MakeMinMaxExtended(p.PressureAvg, p.PressureMax, p.PressureMin),
                    AxialLoad = MakeMinMaxExtended(p.AxialLoadAvg, p.AxialLoadMax, p.AxialLoadMin),
                    Flow = MakeMinMaxExtended(p.FlowAvg, p.FlowMax, p.FlowMin),
                    RotorSpeed = MakeMinMaxExtended(p.RotorSpeedAvg, p.RotorSpeedMax, p.RotorSpeedMin),
                    RotorTorque = MakeMinMaxExtended(p.RotorTorqueAvg, p.RotorTorqueMax, p.RotorTorqueMin)
                };
                return dto;
            });

            return dtos;
        }

        public async Task<IEnumerable<DrillParamsDto>> GetCompositeAllAsync(int idWell,
            CancellationToken token = default)
        {
            var allDrillParamsQuery = db.WellComposites
                .Where(c => c.IdWell == idWell)
                .Join(db.DrillParams,
                    c => c.IdWellSrc,
                    p => p.IdWell,
                    (c, p) => p);

            var allDrillParams = await allDrillParamsQuery
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var compositeWellDrillParamsQuery = db.WellComposites
                .Where(c => c.IdWell == idWell)
                .Join(db.DrillParams,
                    c => new { IdWell = c.IdWellSrc, IdSection = c.IdWellSectionType },
                    p => new { IdWell = p.IdWell, IdSection = p.IdWellSectionType },
                    (c, p) => p);

            var compositeWellDrillParams = await compositeWellDrillParamsQuery
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);

            var result = compositeWellDrillParams.Select(x => Convert(x, allDrillParams));
            return result;
        }

        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(dto, token).ConfigureAwait(false);
            return result;
        }

        private static DrillParamsDto Convert(DrillParams entity, IEnumerable<DrillParams> drillParams)
        {
            return new DrillParamsDto
            {
                Id = entity.Id,
                IdWellSectionType = entity.IdWellSectionType,
                AxialLoad = MakeMinMaxExtended(entity.AxialLoadAvg, entity.AxialLoadMax, entity.AxialLoadMin, drillParams.Select(x => (x.AxialLoadMin, x.AxialLoadMax))),
                Depth = new MinMaxDto<double> {
                    Min = entity.DepthEnd,
                    Max = entity.DepthStart
                },
                Flow = MakeMinMaxExtended(entity.FlowAvg, entity.FlowMax, entity.FlowMin, drillParams.Select(x => (x.FlowMin, x.FlowMax))),
                IdWell = entity.IdWell,
                Pressure = MakeMinMaxExtended(entity.PressureAvg, entity.PressureMax, entity.PressureMin, drillParams.Select(x => (x.PressureMin, x.PressureMax))),
                RotorSpeed = MakeMinMaxExtended(entity.RotorSpeedAvg, entity.RotorSpeedMax, entity.RotorSpeedMin, drillParams.Select(x => (x.RotorSpeedMin, x.RotorSpeedMax))),
                RotorTorque = MakeMinMaxExtended(entity.RotorTorqueAvg, entity.RotorTorqueMax, entity.RotorTorqueMin, drillParams.Select(x => (x.RotorTorqueMin, x.RotorTorqueMax)))
            };
        }

        private static MinMaxExtendedViewDto MakeMinMaxExtended(double avg, double max, double min, IEnumerable<(double min, double max)>? allDrillParams = null)
            => new MinMaxExtendedViewDto {
                    Avg = avg,
                    Max = max,
                    Min = min,
                    IsMax = (! allDrillParams?.Any (mx => mx.max > max)) ?? false,
                    IsMin = (! allDrillParams?.Any (mn => mn.min < min)) ?? false
            };
    }
#nullable disable
}