using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Flows
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.

    public sealed class RetryFlow : FlowBase
    {
        private readonly IFlow _inner;
        private readonly int _maxRetries;
        private readonly TimeSpan _initialDelay;
        private readonly bool _exponentialBackoff;

        public RetryFlow(
            IFlow inner,
            int maxRetries = 3,
            TimeSpan? initialDelay = null,
            bool exponentialBackoff = true)
            : base($"Retry({_innerName(inner)})", inner.MinimumLevel)
        {
            _inner = inner ?? throw new ArgumentNullException(nameof(inner));
            _maxRetries = maxRetries;
            _initialDelay = initialDelay ?? TimeSpan.FromMilliseconds(200);
            _exponentialBackoff = exponentialBackoff;
        }

        public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            int attempt = 0;
            TimeSpan delay = _initialDelay;

            while (true)
            {
                var result = await _inner.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);

                if (result == WriteResult.Success || attempt >= _maxRetries)
                {
                    return result;
                }

                attempt++;

                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);

                if (_exponentialBackoff)
                {
                    delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2);
                }
            }
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default)
            => _inner.FlushAsync(cancellationToken);

        public override ValueTask DisposeAsync()
            => _inner.DisposeAsync();

        private static string _innerName(IFlow flow) => flow.Name ?? flow.GetType().Name;
    }
}
