using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Data.ProcessMaps.Operations;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.WITS;
using AsbCloudApp.IntegrationEvents;
using AsbCloudApp.IntegrationEvents.Interfaces;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Background;
using Mapster;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services;

public class WellInfoService
{
    public class WorkWellInfoUpdate : Work
    {
        public WorkWellInfoUpdate()
            : base("Well statistics update")
        {
            Timeout = TimeSpan.FromMinutes(30);
        }

        protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
        {
            var wellService = services.GetRequiredService<IWellService>();
            var operationsStatService = services.GetRequiredService<IOperationsStatService>();
            var processMapPlanRotorRepository = services.GetRequiredService<IChangeLogRepository<ProcessMapPlanRotorDto, ProcessMapPlanBaseRequestWithWell>>();
            var processMapPlanSlideRepository = services.GetRequiredService<IChangeLogRepository<ProcessMapPlanSlideDto, ProcessMapPlanBaseRequestWithWell>>();
            var subsystemService = services.GetRequiredService<ISubsystemService>();
            var telemetryDataSaubCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
            var messageHub = services.GetRequiredService<IIntegrationEventHandler<UpdateWellInfoEvent>>();

            var entries = await wellService.GetAllAsync(token);
            var wells = entries.ToList();
            var activeWells = wells.Where(well => well.IdState == 1);

            var wellsIds = activeWells.Select(w => w.Id);

            var processMapPlanWellDrillingRequests = wellsIds.Select(id => new ProcessMapPlanBaseRequestWithWell(id));
            var processMapPlanWellDrillings = new List<ProcessMapPlanBaseDto>();
            foreach (var processMapPlanWellDrillingRequest in processMapPlanWellDrillingRequests)
            {
                var processMapsRotor = await processMapPlanRotorRepository.GetCurrent(processMapPlanWellDrillingRequest, token);
                var processMapsSlide = await processMapPlanSlideRepository.GetCurrent(processMapPlanWellDrillingRequest, token);
                
                processMapPlanWellDrillings.AddRange(processMapsRotor);
                processMapPlanWellDrillings.AddRange(processMapsSlide);
            }

            var wellDepthByProcessMap = processMapPlanWellDrillings
                .GroupBy(p => p.IdWell)
                .Select(g => new
                {
                    Id = g.Key,
                    DepthEnd = g.Max(p => p.DepthEnd)
                });

            var operationsStat = await operationsStatService.GetWellsStatAsync(wellsIds, token);

            var subsystemStat = await subsystemService
                .GetStatByActiveWells(wellsIds, token);
            subsystemStat = subsystemStat.ToArray();

            var count = activeWells.Count();
            var i = 0d;
            WellMapInfo = activeWells.Select(well =>
            {
                var wellMapInfo = well.Adapt<WellMapInfoWithCompanies>();
                wellMapInfo.IdState = well.IdState;
                onProgressCallback($"Start updating info by well({well.Id}): {well.Caption}", i++ / count);
                double? currentDepth = null;

                TelemetryDataSaubDto? lastSaubTelemetry = null;

                if (well.IdTelemetry.HasValue)
                {
                    wellMapInfo.IdTelemetry = well.IdTelemetry.Value;
                    lastSaubTelemetry = telemetryDataSaubCache.GetLastOrDefault(well.IdTelemetry.Value);
                    if (lastSaubTelemetry is not null)
                    {
                        currentDepth = lastSaubTelemetry.WellDepth;
                    }
                }

                var wellOperationsStat = operationsStat.FirstOrDefault(s => s.Id == well.Id);
                var wellLastFactSection = wellOperationsStat?.Sections.OrderBy(s => s.Fact?.WellDepthStart).LastOrDefault(s => s.Fact is not null);
                currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd;

                var wellProcessMaps = processMapPlanWellDrillings
                    .Where(p => p.IdWell == well.Id)
                    .OrderBy(p => p.DepthEnd);

                int? idSection = wellLastFactSection?.Id;
                ProcessMapPlanBaseDto? processMapPlanWellDrilling = null;

                if (idSection.HasValue && currentDepth.HasValue)
                {
                    processMapPlanWellDrilling = wellProcessMaps
                        .Where(p => p.IdWellSectionType == idSection)
                        .Where(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value)
                        .FirstOrDefault();
                }
                else if (currentDepth.HasValue)
                {
                    processMapPlanWellDrilling = wellProcessMaps.FirstOrDefault(p => p.DepthStart <= currentDepth.Value && p.DepthEnd >= currentDepth.Value);
                }
                else if (idSection.HasValue)
                {
                    processMapPlanWellDrilling = wellProcessMaps.FirstOrDefault(p => p.IdWellSectionType == idSection);
                }

                double? planTotalDepth = null;
                planTotalDepth = wellDepthByProcessMap.FirstOrDefault(p => p.Id == well.Id)?.DepthEnd;
                planTotalDepth ??= wellOperationsStat?.Total.Plan?.WellDepthEnd;

                wellMapInfo.Section = wellLastFactSection?.Caption;

                wellMapInfo.FirstFactOperationDateStart = wellOperationsStat?.Total.Fact?.Start
                    ?? wellOperationsStat?.Total.Plan?.Start;

                wellMapInfo.LastPredictOperationDateEnd = wellOperationsStat?.Total.Plan?.End;

                wellMapInfo.AxialLoad = new()
                {
                    Fact = lastSaubTelemetry?.AxialLoad
                };

                wellMapInfo.TopDriveSpeed = new()
                {
                    Fact = lastSaubTelemetry?.RotorSpeed
                };

                wellMapInfo.TopDriveTorque = new()
                {
                    Fact = lastSaubTelemetry?.RotorTorque
                };

                wellMapInfo.Pressure = new()
                {
                    Fact = lastSaubTelemetry?.Pressure
                };

                wellMapInfo.PressureSp = lastSaubTelemetry?.PressureSp;

                wellMapInfo.WellDepth = new()
                {
                    Plan = planTotalDepth,
                    Fact = currentDepth,
                };

                wellMapInfo.ROP = new()
                {
                    Fact = wellOperationsStat?.Total.Fact?.Rop,
                };

                wellMapInfo.RaceSpeed = new()
                {
                    Plan = wellOperationsStat?.Total.Plan?.RouteSpeed,
                    Fact = wellOperationsStat?.Total.Fact?.RouteSpeed,
                };

                if(processMapPlanWellDrilling is ProcessMapPlanRotorDto processMapPlanRotor)
                {
                    wellMapInfo.AxialLoad.Plan = processMapPlanRotor?.WeightOnBit;

                    wellMapInfo.TopDriveSpeed.Plan = processMapPlanRotor?.Rpm;

                    wellMapInfo.TopDriveTorque.Plan = processMapPlanRotor?.TopDriveTorque;

                    wellMapInfo.Pressure.Plan = processMapPlanRotor?.DifferentialPressure;

                    wellMapInfo.ROP.Plan = processMapPlanRotor?.RopMax;
                }

                if (processMapPlanWellDrilling is ProcessMapPlanSlideDto processMapPlanSlide)
                {
                    wellMapInfo.AxialLoad.Plan = processMapPlanSlide?.WeightOnBit;

                    wellMapInfo.Pressure.Plan = processMapPlanSlide?.DifferentialPressure;

                    wellMapInfo.ROP.Plan = processMapPlanSlide?.RopMax;
                }

                var wellSubsystemStat = subsystemStat.FirstOrDefault(s => s.Well.Id == well.Id);
                wellMapInfo.SaubUsage = wellSubsystemStat?.SubsystemAPD?.KUsage ?? 0d;
                wellMapInfo.SpinUsage = wellSubsystemStat?.SubsystemOscillation?.KUsage ?? 0d;
                wellMapInfo.TorqueKUsage = wellSubsystemStat?.SubsystemTorqueMaster?.KUsage ?? 0d;
                wellMapInfo.TvdLagDays = wellOperationsStat?.TvdLagDays;
                wellMapInfo.TvdDrillingDays = wellOperationsStat?.TvdDrillingDays;
                wellMapInfo.IdsCompanies = well.Companies.Select(c => c.Id);

                return wellMapInfo;
            }).ToArray();

            var updateWellInfoEventTasks = wellsIds.Select(idWell =>
                messageHub.HandleAsync(new UpdateWellInfoEvent(idWell), token));

            await Task.WhenAll(updateWellInfoEventTasks);
        }
    }

    class WellMapInfoWithCompanies : WellMapInfoDto
    {
        public int? IdTelemetry { get; set; }
        public IEnumerable<int> IdsCompanies { get; set; } = null!;
    }

    private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
    private readonly ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
    private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository;
    private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository;
    private readonly IGtrRepository gtrRepository;
    private static IEnumerable<WellMapInfoWithCompanies> WellMapInfo = [];

    public WellInfoService(
        ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
        ITelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache,
        IWitsRecordRepository<Record7Dto> witsRecord7Repository,
        IWitsRecordRepository<Record1Dto> witsRecord1Repository,
        IGtrRepository gtrRepository)
    {
        this.telemetryDataSaubCache = telemetryDataSaubCache;
        this.telemetryDataSpinCache = telemetryDataSpinCache;

        this.witsRecord7Repository = witsRecord7Repository;
        this.witsRecord1Repository = witsRecord1Repository;
        this.gtrRepository = gtrRepository;
    }

    private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithCompanies wellInfo)
    {
        var result = wellInfo.Adapt<WellMapInfoWithTelemetryStat>();
        if (wellInfo.IdTelemetry.HasValue)
        {
            var idTelemetry = wellInfo.IdTelemetry.Value;
            result.LastDataSaub = telemetryDataSaubCache.GetLastOrDefault(idTelemetry);
            result.LastDataSpin = telemetryDataSpinCache.GetLastOrDefault(idTelemetry);
            result.LastDataDdsDate = GetLastOrDefaultDdsTelemetry(idTelemetry);
            result.LastDataGtrDate = gtrRepository.GetLastData(wellInfo.Id)
                .MaxOrDefault(item => item.Date);
            result.LastDataDpcsDate = null;
            result.LastDataDpcsDate = null;
        }

        return result;
    }

    private DateTime? GetLastOrDefaultDdsTelemetry(int idTelemetry)
    {
        var lastDdsRecord1Date = witsRecord1Repository.GetLastOrDefault(idTelemetry)?.DateTime;
        var lastDdsRecord7Date = witsRecord7Repository.GetLastOrDefault(idTelemetry)?.DateTime;

        if (lastDdsRecord1Date.HasValue && lastDdsRecord7Date.HasValue)
            if (lastDdsRecord1Date.Value > lastDdsRecord7Date.Value)
                return lastDdsRecord1Date.Value;
            else
                return lastDdsRecord7Date.Value;

        return lastDdsRecord1Date ?? lastDdsRecord7Date;
    }

    public WellMapInfoWithTelemetryStat? FirstOrDefault(Func<WellMapInfoDto, bool> predicate)
    {
        var first = WellMapInfo.FirstOrDefault(predicate);
        if (first is WellMapInfoWithCompanies wellMapInfoWithCompanies)
            return Convert(wellMapInfoWithCompanies);

        return null;
    }
}