using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Background
{
    /// <summary>
    /// Сервис для фонового выполнения работы
    /// </summary>
    public class BackgroundWorker : BackgroundService
    {
        private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10);
        private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(2);
        private static readonly TimeSpan exceptionHandleTimeout = TimeSpan.FromSeconds(2);
        private readonly IServiceProvider serviceProvider;
        private readonly WorkQueue workQueue = new WorkQueue();
        public string? CurrentWorkId;
        public BackgroundWorker(IServiceProvider serviceProvider)
        {
            this.serviceProvider = serviceProvider;
        }

        /// <summary>
        /// Добавление задачи в очередь.
        /// Не периодические задачи будут выполняться вперед.
        /// </summary>
        /// <param name="work"></param>
        /// <exception cref="ArgumentException">Id mast be unique</exception>
        public void Push(WorkBase work)
        {
            workQueue.Push(work);
        }

        /// <summary>
        /// Проверяет наличие работы с указанным Id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool Contains(string id)
        {
            return workQueue.Contains(id);
        }

        /// <summary>
        /// Удаление работы по ID
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool Delete(string id)
        {
            return workQueue.Delete(id);
        }

        protected override async Task ExecuteAsync(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                var dateStart = DateTime.Now;
                var work = workQueue.Pop();
                if (work is null)
                {
                    await Task.Delay(executePeriod, token);
                    continue;
                }
                CurrentWorkId = work.Id;
                using var scope = serviceProvider.CreateScope();
                
                try
                {
                    Trace.TraceInformation($"Backgroud work:\"{work.Id}\" start.");
                    var task = work.ActionAsync(work.Id, scope.ServiceProvider, token);
                    await task.WaitAsync(work.Timeout, token);

                    work.ExecutionTime = DateTime.Now - dateStart;
                    Trace.TraceInformation($"Backgroud work:\"{work.Id}\" done. ExecutionTime: {work.ExecutionTime:hh\\:mm\\:ss\\.fff}");
                }
                catch (Exception exception)
                {
                    Trace.TraceError($"Backgroud work:\"{work.Id}\" throw exception: {exception.Message}");
                    if (work.OnErrorAsync is not null)
                    {
                        using var task = Task.Run(
                            async () => await work.OnErrorAsync(work.Id, exception, token),
                            token);
                        await task.WaitAsync(exceptionHandleTimeout, token);
                    }
                }
                CurrentWorkId = null;
                await Task.Delay(minDelay, token);
            }
        }
    }
}