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

namespace AsbCloudInfrastructure.Services.Subsystems
{
#nullable enable
    internal class SubsystemOperationTimeService : ISubsystemOperationTimeService
    {

        private readonly IAsbCloudDbContext db;
        private readonly IWellService wellService;
        private readonly ICrudService<SubsystemDto> subsystemService;
        


        public SubsystemOperationTimeService(IAsbCloudDbContext db, IWellService wellService, ICrudService<SubsystemDto> subsystemService)
        {
            this.db = db;
            this.wellService = wellService;
            this.subsystemService = subsystemService;
        }

        private async Task<IEnumerable<SubsystemDto>> GetSubsystemAsync(int idWell, CancellationToken token)
        {
            var well = await wellService.GetOrDefaultAsync(idWell, token);
            if (well?.IdTelemetry is null || well.Timezone is null)
                return null;
            var wellSubsystem = await db.SubsystemOperationTimes
                .Include(e => e.Subsystem)
                .AsNoTracking()
                .Where(o => o.IdTelemetry == well.IdTelemetry)
                .DistinctBy(o => o.IdSubsystem)
                .Select(d => new SubsystemDto
                {
                    Id = d.Subsystem.Id,
                    Name = d.Subsystem.Name,
                    Description = d.Subsystem.Description,

                })
                .ToListAsync(token);
            return wellSubsystem;
        }

        public async Task<IEnumerable<SubsystemDto>> GetSubsystemAsync(int? idWell, CancellationToken token)
        {
            if (idWell is null)
            {
                var subsystem = await subsystemService.GetAllAsync(token);
                return subsystem;
            }
            var subsystemWell = await GetSubsystemAsync(idWell, token);
            return subsystemWell;

        }


        public async Task<int> DeleteAsync(SubsystemOperationTimeRequest request, CancellationToken token)
        {
            var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
            if (well?.IdTelemetry is null || well.Timezone is null)
                return 0;
            var query = BuildQuery(request);
            db.SubsystemOperationTimes.RemoveRange(query);
            return await db.SaveChangesAsync(token);
        }

        public async Task<IEnumerable<SubsystemOperationTimeDto>> GetOperationTimeAsync(SubsystemOperationTimeRequest request, CancellationToken token)
        {  
            var query = BuildQuery(request)
                .AsNoTracking();
            if (query is null)
                return null;

            var data = await query.ToListAsync(token);

            if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeInner) 
            {
                if (request.GtDate is not null)
                    query = query.Where(o => o.DateStart >= request.GtDate.Value || o.DateEnd >= request.GtDate.Value);

                if (request.LtDate is not null)
                    query = query.Where(o => o.DateEnd <= request.LtDate.Value || o.DateStart <= request.LtDate.Value);
            }
            if (request.SelectMode == SubsystemOperationTimeRequest.SelectModeTrim)
            {
                var begin = request.GtDate ?? throw new ArgumentNullException(nameof(request.GtDate));
                var end = request.GtDate ?? throw new ArgumentNullException(nameof(request.LtDate));
                data = Trim(data, begin,end );
            }
            var dtos = data.Select(o => Convert(o));
            return dtos;
        }

       

        public async Task<IEnumerable<SubsystemStatDto>?> GetStatAsync(SubsystemOperationTimeRequest request, CancellationToken token)
        {
            var data = await GetOperationTimeAsync(request, token);
            var statList = CalcStat(data, request);
            return statList;
        }

        private List<SubsystemOperationTime> Trim(List<SubsystemOperationTime> data, DateTime gtDate, DateTime ltDate)
        {           
            var itemsToTrim = data.Where(q => true)
                .Select(o => new SubsystemOperationTime
                {
                    Id = o.Id,
                    DateStart = gtDate < o.DateStart ? gtDate : o.DateStart,
                    DateEnd = ltDate > o.DateEnd ? ltDate : o.DateEnd,
                    IdSubsystem = o.IdSubsystem,
                    IdTelemetry = o.IdTelemetry,
                    DepthEnd = o.DepthEnd,
                    DepthStart = o.DepthStart,
                    Subsystem = o.Subsystem,
                    Telemetry = o.Telemetry
                })
                .ToList();
            return itemsToTrim;
        }

        private IEnumerable<SubsystemStatDto> CalcStat(IEnumerable<SubsystemOperationTimeDto> groupedData, SubsystemOperationTimeRequest request)
        {
            var result = new List<SubsystemStatDto>();
            var groupedDataSubsystems = groupedData
                .GroupBy(x => x.IdSubsystem);
            var periodGroupTotal = groupedData
                .GroupBy(x => x.IdSubsystem)
                .Sum(g => g.Sum(o=> (o.DateEnd - o.DateStart).TotalHours));            
            foreach (var item in groupedDataSubsystems)
            {                
                var periodGroup = item.Sum(g => (g.DateEnd - g.DateStart).TotalHours);
                var gtDate = request.GtDate ?? item.Min(d => d.DateStart);
                var ltDate = request.LtDate ?? item.Max(d => d.DateEnd);
                var periodRequest = item.Where(o => o.DateStart>=gtDate  && o.DateEnd <= ltDate)
                    .Sum(o =>  (o.DateEnd - o.DateStart).TotalHours);
                int idSubsystem = item.First().IdSubsystem; 
                var subsystemStat = new SubsystemStatDto()
                {
                    IdSubsystem = idSubsystem,
                    Subsystem = subsystemService.GetOrDefault(idSubsystem)?.Name ?? "unknown",
                    //Subsystem = db.Subsystems.Where(n => n.Id == idSubsystem).FirstOrDefault()?.Name ?? "unknown",
                    UsedTimeHours = TimeSpan.FromHours(periodGroup),
                    KUsage = 1d*periodGroup / periodRequest,
                    K2 = 1d*periodGroup / periodGroupTotal,
                };
                result.Add(subsystemStat);
            }
            return result;
        }
        

        private IQueryable<SubsystemOperationTime>? BuildQuery(SubsystemOperationTimeRequest request)
        {
            var idTelemetry = wellService.GetOrDefault(request.IdWell)?.IdTelemetry;
            if (idTelemetry is null)
                return null;

            var query = db.SubsystemOperationTimes
            .Include(o => o.Subsystem)
            .Where(o => o.IdTelemetry == idTelemetry);
            
            if(request.IdsSubsystems?.Any() == true)
                query = query.Where(o => request.IdsSubsystems.Contains(o.IdSubsystem));

           
            if (request.GtDate is not null)
                query = query.Where(o => o.DateStart >= request.GtDate.Value);

            if (request.LtDate is not null)
                query = query.Where(o => o.DateEnd <= request.LtDate.Value);
           
            
            if (request.GtDepth is not null)
                query = query.Where(o => o.DepthStart >= request.GtDepth.Value);

            if (request.LtDepth is not null)
                query = query.Where(o => o.DepthEnd <= request.LtDepth.Value);                
            

            if (request?.SortFields?.Any() == true)
            {
                query = query.SortBy(request.SortFields);
            }
            else
                query = query
                    .OrderBy(o => o.DateStart)
                    .ThenBy(o => o.DepthStart);

            if (request?.Skip > 0)
                query = query.Skip((int)request.Skip);

            if (request?.Take > 0)
                query = query.Take((int)request.Take);
            else
                query = query.Take(3000);

            return query;
        }

        private SubsystemOperationTimeDto Convert(SubsystemOperationTime operationTime)
        {
            var dto = operationTime.Adapt<SubsystemOperationTimeDto>();           
            return dto;
        }



    }
#nullable disable
}