using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMap;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Data.WITS;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.Subsystems;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Background;
using AsbCloudInfrastructure.Services.SAUB;
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
    {
        class WellMapInfoWithComanies : WellMapInfoDto
        {
            public int? IdTelemetry { get; set; }
            public IEnumerable<int> IdsCompanies { get; set; } = null!;
        }

        private const string workId = "Well statistics update";
        private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30);
        
        private readonly TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache;
        private readonly TelemetryDataCache<TelemetryDataSpinDto> telemetryDataSpinCache;
        private readonly IWitsRecordRepository<Record7Dto> witsRecord7Repository;
        private readonly IWitsRecordRepository<Record1Dto> witsRecord1Repository;
        private readonly IGtrRepository gtrRepository;
        private static IEnumerable<WellMapInfoWithComanies> WellMapInfo = Enumerable.Empty<WellMapInfoWithComanies>();
        
        public WellInfoService(
            TelemetryDataCache<TelemetryDataSaubDto> telemetryDataSaubCache,
            TelemetryDataCache<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;
        }

        public static WorkPeriodic MakeWork()
        {
            var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod)
            {
                Timeout = TimeSpan.FromMinutes(30)
            };
            return workPeriodic;
        }

        private static async Task WorkAction(string workName, IServiceProvider serviceProvider, CancellationToken token)
        {
            var db = serviceProvider.GetRequiredService<IAsbCloudDbContext>();
            var wellService = serviceProvider.GetRequiredService<IWellService>();
            var operationsStatService = serviceProvider.GetRequiredService<IOperationsStatService>();
            var processMapRepository = serviceProvider.GetRequiredService<IProcessMapPlanRepository>();
            var subsystemOperationTimeService = serviceProvider.GetRequiredService<ISubsystemOperationTimeService>();
            var telemetryDataSaubCache = serviceProvider.GetRequiredService<TelemetryDataCache<TelemetryDataSaubDto>>();

            var activeWells = await wellService.GetAsync(new() {IdState = 1}, token);

            IEnumerable<int> activeWellsIds = activeWells
                .Select(w => w.Id);

            var idTelemetries = activeWells
                    .Where(w => w.IdTelemetry != null)
                    .Select(t => t.IdTelemetry);

            var processMapRequests = activeWellsIds.Select(id => new ProcessMapRequest { IdWell = id });
            var processMaps = await processMapRepository.GetProcessMapAsync(processMapRequests, token);

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

            var operationsStat = await operationsStatService.GetWellsStatAsync(activeWellsIds, token);
            
            var subsystemStat = await subsystemOperationTimeService.GetStatByActiveWells(activeWellsIds, token);

            WellMapInfo = activeWells.Select(well => {
                var wellMapInfo = well.Adapt<WellMapInfoWithComanies>();
                wellMapInfo.IdState = well.IdState;

                double? currentDepth = null;

                if (well.IdTelemetry.HasValue)
                {
                    wellMapInfo.IdTelemetry = well.IdTelemetry.Value;
                    var 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.LastOrDefault(s => s.Fact is not null);
                currentDepth ??= wellLastFactSection?.Fact?.WellDepthEnd;

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

                int? idSection = wellLastFactSection?.Id;
                ProcessMapPlanDto? welllProcessMap = null;

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

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

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

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

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

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

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

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

                return wellMapInfo;
            }).ToArray();
        }

        private WellMapInfoWithTelemetryStat Convert(WellMapInfoWithComanies 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 WellMapInfoWithComanies wellMapInfoWithComanies)
                return Convert(wellMapInfoWithComanies);

            return null;
        }
    }
}