using System.Diagnostics;
using OpenCvSharp;
using Serilog;
using VisionBuilder.UI.Common.Commands;
using VisionBuilder.UI.Common.ViewModel.Classes;

namespace VisionBuilder.UI.Common.Processing;

public abstract class BaseRecognitionControl: IRecognitionControl
{
    private readonly BaseRecognitionControlSettings _settings;
    private readonly ILoadingService _loadingService;

    private CancellationTokenSource _cts;
    private Task _loopTask;

    public BaseRecognitionControl(BaseRecognitionControlSettings settings, ILoadingService loadingService)
    {
        _settings = settings;
        _loadingService = loadingService;
    }

    public abstract List<RecipeData> GetRecipesData();
    

    RecipeData _currentRecipe;

    public void SetRecipe(RecipeData recipe)
    {
        lock (_lock)
        {


            // error if running
            if (IsRunning)
            {
                throw new InvalidOperationException("Cannot change recipe while recognition is running.");
            }

            _currentRecipe = recipe;
        }
    }

    public bool IsRunning { get; set; }
    public bool IsPaused { get; set; }

    public RecipeData CurrentRecipe => _currentRecipe;

    public ILoadingService LoadingService => _loadingService;

    private DateTime _startTime;

    object _lock = new object();

    public void Start()
    {
        lock (_lock)
        {


            if (_currentRecipe == null)
            {
                throw new InvalidOperationException("No recipe set.");
            }

            if (IsRunning)
            {
                throw new InvalidOperationException("Recognition is already running.");
            }

            _loadingService.StartLoading($"Starting recipe {_currentRecipe.RecipeName} on {_settings.CameraName}...");
            Initialize(_currentRecipe);
            _startTime = DateTime.Now;
            IsRunning = true;
            _cts = new CancellationTokenSource();


            Log.Information($"Warming up");
            try
            {
                var swWarmup = Stopwatch.StartNew();
                WarmUp();
                swWarmup.Stop();
                Log.Information($"Warmup run took {swWarmup.ElapsedMilliseconds} ms");
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error during warmup run");
            }

            _loopTask = Task.Run(() => Loop(_cts.Token), _cts.Token);

            SessionStarted?.Invoke(new SessionStartedEvent
            {
                RecipeName = _currentRecipe.RecipeName,
                SessionStarted = _startTime,

            });
            _loadingService.StopLoading($"Starting recipe {_currentRecipe.RecipeName} on {_settings.CameraName}...");

            

        }
    }

    protected abstract void Initialize(RecipeData currentRecipe);

    protected abstract void WarmUp();

    protected abstract (Mat originalImage, Mat analysisImage, TimeSpan processingTime, TimeSpan acquisitionTime, string[] errorNames)? ProcessImage(CancellationToken token);

    public void Stop()
    {
        lock (_lock)
        {
            if (!IsRunning)
                return;



            _loadingService.StartLoading($"Stopping {_settings.CameraName}");
            _cts?.Cancel();
            try
            {
                _loopTask?.Wait();
            }
            catch (TaskCanceledException)
            {
                // Ignore cancellation exceptions
            }
            catch (AggregateException ex) when (ex.Flatten().InnerExceptions.Any(x => x is TaskCanceledException))
            {
                // Ignore cancellation exceptions
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error while stopping recognition loop");
            }
            finally
            {
                IsRunning = false;
                _cts?.Dispose();
                _cts = null;
                _loopTask = null;
                Cleanup();
            }

            SessionEnded?.Invoke(new SessionEndedEvent
            {
                SessionEnded = DateTime.Now
            });
            _loadingService.StopLoading($"Stopping {_settings.CameraName}");
            // note: recipe can be stared paused
            //IsPaused = false; // ensure paused is reset
        }
    }

    public void Pause()
    {
        lock (_lock)
        {
            Log.Information("Pausing recognition");
            IsPaused = true;
        }
    }

    public void Resume()
    {
        lock (_lock)
        {
            Log.Information("Resuming recognition");
            IsPaused = false;
        }
    }

    int _errorsInSequence = 0;

    private async Task Loop(CancellationToken token)
    {
     

        while (!token.IsCancellationRequested)
        {
            if (IsPaused)
            {
                await Task.Delay(100, token).ConfigureAwait(false);
                continue;
            }

            var processed = ProcessImage(token);

            if (processed == null)
            {
                return;
            }
            int sleepTime=0;
            if (processed.Value.processingTime.Milliseconds < _settings.MinimumProcessingTime)
            {
                
                sleepTime = _settings.MinimumProcessingTime - (int)processed.Value.processingTime.Milliseconds;
                Log.Debug("Sleeping for {sleepTime}",sleepTime);
                Thread.Sleep(sleepTime);
            }

            if (processed.Value.errorNames.Length > 0)
            {
                _errorsInSequence++;
                if (_errorsInSequence >= _settings.ErrorsInSequenceAlarm)
                {
                    ErrorsInSequenceAlarm(new ErrorsInSequenceEvent(){Errors = _errorsInSequence});
                }
            }
            else
            {
                _errorsInSequence = 0;
            }

            var processingResult = new ImageProcessedEvent()
            {
                SessionStart = _startTime,
                RecipeName = _currentRecipe.RecipeName,
                AnalysisTime = processed.Value.processingTime - processed.Value.acquisitionTime,
                AcquisitionTime = processed.Value.acquisitionTime,
                SleepTime = TimeSpan.FromMilliseconds(sleepTime),
                ErrorNames = processed.Value.errorNames.ToList(),
                HasError = processed.Value.errorNames.Length > 0,
                ImageSource = _settings.CameraName,
                ImageOriginal = processed.Value.originalImage,
                ImageAnalysis = processed.Value.analysisImage
            };

            var swProcessed = Stopwatch.StartNew();
            ImageProcessed(processingResult);
            swProcessed.Stop();
            Log.Information("ImageProcessed event handled in {processed} ms", swProcessed.ElapsedMilliseconds);


        }
    }

    public event Action<ImageProcessedEvent> ImageProcessed = delegate { };
    public event Action<SessionStartedEvent> SessionStarted = delegate { };
    public event Action<SessionEndedEvent> SessionEnded = delegate { };
    public event Action<ErrorsInSequenceEvent> ErrorsInSequenceAlarm = delegate { };

    public event Action RecipeFileChanged = delegate { };
    public virtual void HotReload() { }
    protected void RaiseRecipeFileChanged() => RecipeFileChanged?.Invoke();
    protected virtual void Cleanup() { }
}