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

namespace AsbCloudInfrastructure.Repository
{
    public class SlipsStatRepository : ISlipsStatRepository
    {
        private readonly IAsbCloudDbContext db;
        public SlipsStatRepository(IAsbCloudDbContext db)
        {
            this.db = db;
        }

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

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

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

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

            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)
                .Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory))
                .Include(o => o.WellSectionType)
                .AsNoTracking();

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

            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.FirstOrDefault()!.WellSectionType.Caption
                });

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

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

            TimeSpan? durationMinutesMin = request.DurationMinutesMin.HasValue
                ? new TimeSpan(0, request.DurationMinutesMin.Value, 0)
                : null;
            TimeSpan? durationMinutesMax = request.DurationMinutesMax.HasValue
                ? new TimeSpan(0, request.DurationMinutesMax.Value, 0)
                : null;

            if (durationMinutesMin.HasValue && durationMinutesMax.HasValue)
            {
                detectedOperationsQuery = detectedOperationsQuery
                   .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin.Value
                    && o.DateEnd - o.DateStart <= durationMinutesMax.Value);
            }
            else if (durationMinutesMin.HasValue && !durationMinutesMax.HasValue)
            {
                detectedOperationsQuery = detectedOperationsQuery
                    .Where(o => o.DateEnd - o.DateStart >= durationMinutesMin.Value);
            }
            else if (!durationMinutesMin.HasValue && durationMinutesMax.HasValue)
            {
                detectedOperationsQuery = detectedOperationsQuery
                    .Where(o => o.DateEnd - o.DateStart <= durationMinutesMax.Value);
            }

            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
                    && new TimeDto(s.ShiftStart) <= new TimeDto(o.DateStart.DateTime)
                    && new TimeDto(s.ShiftEnd) >= new TimeDto(o.DateStart.DateTime))
                   ?.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
                        && new TimeDto(s.ShiftStart) <= new TimeDto(o.DateStart.DateTime)
                        && new TimeDto(s.ShiftEnd) >= new TimeDto(o.DateStart.DateTime))
                       ?.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!.Name} {group.First().Driller!.Patronymic} {group.First().Driller!.Surname}",
                SlipsCount = group.Count(),
                SlipsTimeInMinutes = group
                    .Sum(y => (y.Operation.DateEnd - y.Operation.DateStart).TotalMinutes),
                SlipsDepth = 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;
        }
    }
}