using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Background;

/// <summary>
/// Сервис для фонового выполнения работы
/// </summary>
public class BackgroundWorker : BackgroundService
{
    private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1);
    private readonly IServiceProvider serviceProvider;

    /// <summary>
    /// Очередь работ
    /// </summary>
    private Queue<Work> works = new(8);

    /// <summary>
    /// Список периодических работ
    /// </summary>
    public IEnumerable<Work> Works => works;

    /// <summary>
    /// Работа выполняемая в данный момент
    /// </summary>
    public Work? CurrentWork;
    private bool isRuning;

    /// <summary>
    /// последние 16 завершившиеся с ошибкой
    /// </summary>
    public CyclicArray<Work> Felled { get; } = new(16);

    /// <summary>
    /// последние 16 успешно завершенных
    /// </summary>
    public CyclicArray<Work> Done { get; } = new(16);

    /// <summary>
    /// Ошибка в главном цикле, никогда не должна появляться
    /// </summary>
    public string MainLoopLastException { get; private set; } = string.Empty;

    public BackgroundWorker(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken token)
    {
        if (isRuning)
            return;
        isRuning = true;
        Trace.TraceInformation($"{GetType().Name} started");
        while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork))
        {
            try
            {
                using var scope = serviceProvider.CreateScope();

                var result = await CurrentWork.Start(scope.ServiceProvider, token);

                if (!result)
                    Felled.Add(CurrentWork);
                else
                    Done.Add(CurrentWork);

                CurrentWork = null;
                await Task.Delay(minDelay, token);
            }
            catch (Exception ex)
            {
                MainLoopLastException = $"BackgroundWorker " +
                    $"MainLoopLastException: \r\n" +
                    $"date: {DateTime.Now:O}\r\n" +
                    $"message: {ex.Message}\r\n" +
                    $"inner: {ex.InnerException?.Message}\r\n" +
                    $"stackTrace: {ex.StackTrace}";
                Trace.TraceError(MainLoopLastException);
            }
        }
        isRuning = false;
    }

    /// <summary>
    /// Добавить в очередь
    /// <para>
    /// work.Id может быть не уникальным,
    /// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id
    /// </para>
    /// </summary>
    /// <param name="work"></param>
    public void Enqueue(Work work)
    {
        works.Enqueue(work);
        if (ExecuteTask is null || ExecuteTask.IsCompleted)
            StartAsync(CancellationToken.None);
    }

    /// <summary>
    /// Удаление работы по ID из одноразовой очереди
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public bool TryRemoveFromQueue(string id)
    {
        var work = Works.FirstOrDefault(w => w.Id == id);
        if (work is not null)
        {
            works = new Queue<Work>(Works.Where(w => w.Id != id));
            return true;
        }

        return false;
    }
}