using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.DependencyInjection;

namespace AsbCloudInfrastructure.Services.DetectOperations;

public class WorkOperationDetection: Work
{
    private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
    {
        new DetectorDrilling(),
        new DetectorSlipsTime()
        // new DetectorRotor(),
        // new DetectorSlide(),
        //new DetectorDevelopment(),
        //new DetectorTemplating(),
        //new DetectorStaticSurveying(),
        //new DetectorFlashingBeforeConnection(),
        //new DetectorFlashing(),
        //new DetectorTemplatingWhileDrilling(),
    };

    public WorkOperationDetection()
        :base("Operation detection")
    {
        Timeout = TimeSpan.FromMinutes(20);
        OnErrorAsync = (id, exception, token) =>
        {
            var text = $"work {id}, when {CurrentState?.State}, throw error:{exception.Message}";
            Trace.TraceWarning(text);
            return Task.CompletedTask;
        };
    }

    protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
    {
        using var db = services.GetRequiredService<IAsbCloudDbContext>();

        var lastDetectedDates = await db.DetectedOperations
            .GroupBy(o => o.IdTelemetry)
            .Select(g => new
            {
                IdTelemetry = g.Key,
                LastDate = g.Max(o => o.DateEnd)
            })
            .ToListAsync(token);

        var telemetryIds = await db.Telemetries
            .Where(t => t.Info != null && t.TimeZone != null)
            .Select(t => t.Id)
            .ToListAsync(token);

        var joinedlastDetectedDates = telemetryIds
            .GroupJoin(lastDetectedDates,
                t => t,
                o => o.IdTelemetry,
                (outer, inner) => new
                {
                    IdTelemetry = outer,
                    inner.SingleOrDefault()?.LastDate,
                });

        var affected = 0;
        var count = joinedlastDetectedDates.Count();
        var i = 0d;
        foreach (var item in joinedlastDetectedDates)
        {
            var stopwatch = Stopwatch.StartNew();
            var startDate = item.LastDate ?? DateTimeOffset.MinValue;
            onProgressCallback($"start detecting telemetry: {item.IdTelemetry} from {startDate}", i++ / count);
            var newOperations = await DetectOperationsAsync(item.IdTelemetry, startDate, db, token);
            stopwatch.Stop();
            if (newOperations.Any())
            {
                db.DetectedOperations.AddRange(newOperations);
                affected += await db.SaveChangesAsync(token);
            }
        }
    }

    private static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
    {
        var query = db.TelemetryDataSaub
            .AsNoTracking()
            .Where(d => d.IdTelemetry == idTelemetry)
            .Where(d => d.BlockPosition >= 0)
            .Select(d => new DetectableTelemetry
            {
                DateTime = d.DateTime,
                IdUser = d.IdUser,
                Mode = d.Mode,
                WellDepth = d.WellDepth,
                Pressure = d.Pressure,
                HookWeight = d.HookWeight,
                BlockPosition = d.BlockPosition,
                BitDepth = d.BitDepth,
                RotorSpeed = d.RotorSpeed,
            })
            .OrderBy(d => d.DateTime);

        var take = 4 * 86_400; // 4 дня
        var startDate = begin;
        var detectedOperations = new List<DetectedOperation>(8);
        DetectedOperation? lastDetectedOperation = null;
        const int minOperationLength = 5;
        const int maxDetectorsInterpolationFrameLength = 30;
        const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;

        while (true)
        {
            var data = await query
                .Where(d => d.DateTime > startDate)
                .ToArrayAsync(token);

            if (data.Length < gap)
                break;

            var isDetected = false;
            var positionBegin = 0;
            var positionEnd = data.Length - gap;
            while (positionEnd > positionBegin)
            {
                foreach (var detector in detectors)
                {
                    if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
                        continue;
					
                    detectedOperations.Add(result!.Operation);
                    lastDetectedOperation = result.Operation;
                    isDetected = true;
                    positionBegin = result.TelemetryEnd;
                    break;
                }
				
                positionBegin += 1;
            }

            if (isDetected)
                startDate = lastDetectedOperation!.DateEnd;
            else
                startDate = data[positionEnd].DateTime;
        }

        return detectedOperations;
    }
}