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

namespace AsbCloudInfrastructure.Services;

public class SlipsStatService : ISlipsStatService
{
    private readonly IAsbCloudDbContext db;

    public SlipsStatService(IAsbCloudDbContext db)
    {
        this.db = db;
    }

    public async Task<IEnumerable<SlipsStatDto>> GetAllAsync(OperationStatRequest request, CancellationToken token)
    {
        if (request.DateStartUTC.HasValue)
            request.DateStartUTC = DateTime.SpecifyKind(request.DateStartUTC.Value, DateTimeKind.Utc);

        if (request.DateEndUTC.HasValue)
            request.DateEndUTC = DateTime.SpecifyKind(request.DateEndUTC.Value, DateTimeKind.Utc);

        var schedulesQuery = db.Schedule
            .Include(s => s.Well)
            .Include(s => s.Driller)
            .AsNoTracking();

        if (request.DateStartUTC.HasValue && request.DateEndUTC.HasValue)
            schedulesQuery = schedulesQuery.
                Where(s => s.DrillStart >= request.DateStartUTC && s.DrillEnd <= request.DateEndUTC);

        var schedules = await schedulesQuery.ToArrayAsync(token);

        var wells = schedules
            .Select(d => d.Well)
            .Where(well => well.IdTelemetry != null)
            .GroupBy(w => w.Id)
            .ToDictionary(g => g.Key, g => g.First().IdTelemetry!.Value);

        var idsWells = wells.Keys;
        var idsTelemetries = wells.Values;
        var telemetries = wells.ToDictionary(wt => wt.Value, wt => wt.Key);

        var factWellOperationsQuery = db.WellOperations
            .Where(o => idsWells.Contains(o.IdWell))
            .Where(o => o.IdType == 1)
            .Include(o => o.WellSectionType)
            .AsNoTracking();

        if (request.DateStartUTC.HasValue && request.DateEndUTC.HasValue)
            factWellOperationsQuery = factWellOperationsQuery
                .Where(o => o.DateStart.AddHours(o.DurationHours) > request.DateStartUTC && o.DateStart < request.DateEndUTC);

        var factWellOperations = await factWellOperationsQuery.ToArrayAsync(token);

        var sections = factWellOperations
            .GroupBy(o => new { o.IdWell, o.IdWellSectionType })
            .Select(g => new
            {
                g.Key.IdWell,
                g.Key.IdWellSectionType,
                DepthStart = g.Min(o => o.DepthStart),
                DepthEnd = g.Max(o => o.DepthEnd),
                g.First().WellSectionType.Caption
            });

        var detectedOperationsQuery = db.DetectedOperations
            .Where(o => idsTelemetries.Contains(o.IdTelemetry))
            .Where(o => o.IdCategory == WellOperationCategory.IdSlipsTime)
            .AsNoTracking();

        if (request.DateStartUTC.HasValue)
            detectedOperationsQuery = detectedOperationsQuery
                .Where(o => o.DateEnd > request.DateStartUTC);

        if (request.DateEndUTC.HasValue)
            detectedOperationsQuery = detectedOperationsQuery
                .Where(o => o.DateStart < request.DateEndUTC);

        if (request.DurationMinutesMin.HasValue)
        {
            var durationMinutesMin = TimeSpan.FromMinutes(request.DurationMinutesMin.Value);
            detectedOperationsQuery = detectedOperationsQuery
                .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin);
        }

        if (request.DurationMinutesMax.HasValue)
        {
            var durationMinutesMax = TimeSpan.FromMinutes(request.DurationMinutesMax.Value);
            detectedOperationsQuery = detectedOperationsQuery
                .Where(o => o.DateEnd - o.DateStart <= durationMinutesMax);
        }

        var detectedOperations = await detectedOperationsQuery
            .ToArrayAsync(token);

        var detectedOperationsGroupedByDrillerAndSection = detectedOperations.Select(o => new
            {
                Operation = o,
                IdWell = telemetries[o.IdTelemetry],
                schedules.FirstOrDefault(s =>
                        s.IdWell == telemetries[o.IdTelemetry] &&
                        s.DrillStart <= o.DateStart &&
                        s.DrillEnd > o.DateStart && (
                            s.ShiftStart > s.ShiftEnd
                        ) ^ (s.ShiftStart <= new TimeDto(o.DateStart.DateTime).MakeTimeOnly() &&
                             s.ShiftEnd > new TimeDto(o.DateStart.DateTime).MakeTimeOnly()
                        ))
                    ?.Driller,
                Section = sections.FirstOrDefault(s =>
                    s.IdWell == telemetries[o.IdTelemetry]
                    && s.DepthStart <= o.DepthStart
                    && s.DepthEnd >= o.DepthStart)
            })
            .Where(o => o.Driller != null)
            .Where(o => o.Section != null)
            .Select(o => new
            {
                o.Operation,
                o.IdWell,
                Driller = o.Driller!,
                Section = o.Section!
            })
            .GroupBy(o => new { o.Driller.Id, o.Section.IdWellSectionType });


        var factWellOperationsGroupedByDrillerAndSection = factWellOperations
            .Select(o => new
            {
                Operation = o,
                schedules.FirstOrDefault(s =>
                        s.IdWell == o.IdWell &&
                        s.DrillStart <= o.DateStart &&
                        s.DrillEnd > o.DateStart && (
                            s.ShiftStart > s.ShiftEnd
                        ) ^ (s.ShiftStart <= new TimeDto(o.DateStart.DateTime).MakeTimeOnly() &&
                             s.ShiftEnd > new TimeDto(o.DateStart.DateTime).MakeTimeOnly()
                        ))
                    ?.Driller,
            })
            .Where(o => o.Driller != null)
            .GroupBy(o => new { o.Driller!.Id, o.Operation.IdWellSectionType });
        
        var stats = detectedOperationsGroupedByDrillerAndSection.Select(group => new SlipsStatDto
        {
            DrillerName = $"{group.First().Driller!.Surname} {group.First().Driller!.Name} {group.First().Driller!.Patronymic}",
            SlipsCount = group.Count(),
            SlipsTimeInMinutes = group
                .Sum(y => (y.Operation.DateEnd - y.Operation.DateStart).TotalMinutes),
            SectionDepth = factWellOperationsGroupedByDrillerAndSection
                .Where(o => o.Key.Id == group.Key.Id)
                .Where(o => o.Key.IdWellSectionType == group.Key.IdWellSectionType)
                .Sum(o => o.Max(op => op.Operation.DepthEnd) - o.Min(op => op.Operation.DepthStart)),
            SectionCaption = group.First().Section.Caption,
            WellCount = group.GroupBy(g => g.IdWell).Count(),
        });

        return stats;
    }
}