using AsbCloudApp.Data;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Background;

/// <summary>
/// Класс разовой работы.
/// Разовая работа приоритетнее периодической.
/// </summary>
public abstract class Work : BackgroundWorkDto
{
    private CancellationTokenSource? stoppingCts;

    private sealed class WorkBase : Work
    {
        private Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> ActionAsync { get; }
        public WorkBase(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync)
            : base(id)
        {
            ActionAsync = actionAsync;
        }

        protected override Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
            => ActionAsync(id, services, onProgressCallback, token);
    }

    /// <summary>
    /// Делегат обработки ошибки.
    /// Не должен выполняться долго.
    /// </summary>
    public Func<string, Exception, CancellationToken, Task>? OnErrorAsync { get; set; }

    /// <summary>
    /// макс продолжительность обработки исключения
    /// </summary>
    public TimeSpan OnErrorHandlerTimeout { get; set; } = TimeSpan.FromSeconds(5);

    /// <summary>
    /// Базовая работа
    /// </summary>
    /// <param name="id"></param>
    public Work(string id)
    {
        Id = id;
    }

    /// <summary>
    /// Создать работу на основе делегата
    /// </summary>
    /// <param name="id"></param>
    /// <param name="actionAsync"></param>
    /// <returns></returns>
    [Obsolete("Use implement Work class")]
    public static Work CreateByDelegate(string id, Func<string, IServiceProvider, Action<string, double?>, CancellationToken, Task> actionAsync) 
    { 
        return new WorkBase(id, actionAsync);
    }

    /// <summary>
    /// Запустить работу
    /// </summary>
    /// <param name="services"></param>
    /// <param name="token"></param>
    /// <returns>True - success, False = fail</returns>
    public async Task<bool> Start(IServiceProvider services, CancellationToken token)
    {
        SetStatusStart();
        try
        {
            stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(token);
            var task = Action(Id, services, UpdateStatus, stoppingCts.Token);
            await task.WaitAsync(Timeout, stoppingCts.Token);
            SetStatusComplete();
            return true;
        }
        catch (Exception exception)
        {
            var message = FormatExceptionMessage(exception);
            SetLastError(message);
            if (OnErrorAsync is not null)
            {
                try
                {
                    var task = Task.Run(
                        async () => await OnErrorAsync(Id, exception, token),
                        token);
                    await task.WaitAsync(OnErrorHandlerTimeout, token);
                }
                catch (Exception onErrorAsyncException)
                {
                    var message2 = FormatExceptionMessage(onErrorAsyncException);
                    Trace.TraceError($"Backgroud work:\"{Id}\" OnError handler throws exception: {message2}");
                }
            }
        }
        return false;
    }

    public void Stop()
    {
        stoppingCts?.Cancel();
    }

    private static string FormatExceptionMessage(Exception exception)
    {
        var firstException = FirstException(exception);
        var message = new StringBuilder();
        if (firstException != exception) 
        {
            message.Append("top exception:");
            message.AppendLine(exception.Message);

            message.Append("inner exception:");
            message.AppendLine(firstException.Message);
        }
        else
            message.AppendLine(firstException.Message);
        message.AppendLine(exception.StackTrace);
        return message.ToString();
    }

    private static Exception FirstException(Exception exception)
    {
        if (exception.InnerException is not null)
            return FirstException(exception.InnerException);
        return exception;
    }

    /// <summary>
    /// делегат фоновой работы
    /// </summary>
    /// <param name="id">Идентификатор работы</param>
    /// <param name="services">Поставщик сервисов</param>
    /// <param name="onProgressCallback">on progress callback. String - new state text. double? - optional progress 0-100%</param>
    /// <param name="token"></param>
    /// <returns></returns>
    protected abstract Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token);
}