using System;
using System.Threading;
using System.Threading.Tasks;
using MarcusLaw.OutlookAddin.Core.Models;
using MarcusLaw.OutlookAddin.Core.Services;
using Serilog;

namespace OutlookAddin.Services
{
    /// <summary>
    /// Drains <see cref="IRetryQueue"/> every <see cref="Interval"/>. Each item
    /// gets up to <see cref="MaxAttempts"/> tries before being abandoned in
    /// the queue (kept on disk so the user can inspect later).
    /// </summary>
    public sealed class RetryQueueProcessor : IDisposable
    {
        public TimeSpan Interval { get; } = TimeSpan.FromSeconds(30);
        public int MaxAttempts { get; } = 3;

        private readonly IRetryQueue _queue;
        private readonly IEspoCrmClient _client;
        private readonly ILogger _logger;
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
        private Task? _loopTask;

        public RetryQueueProcessor(IRetryQueue queue, IEspoCrmClient client, ILogger logger)
        {
            _queue = queue ?? throw new ArgumentNullException(nameof(queue));
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Start()
        {
            if (_loopTask != null) return;
            _loopTask = Task.Run(() => RunLoopAsync(_cts.Token));
        }

        private async Task RunLoopAsync(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                try
                {
                    await DrainOnceAsync(ct).ConfigureAwait(false);
                }
                catch (OperationCanceledException) { return; }
                catch (Exception ex)
                {
                    _logger.Warning(ex, "RetryQueueProcessor: drain failed");
                }

                try { await Task.Delay(Interval, ct).ConfigureAwait(false); }
                catch (OperationCanceledException) { return; }
            }
        }

        private async Task DrainOnceAsync(CancellationToken ct)
        {
            var items = await _queue.ListAllAsync(ct).ConfigureAwait(false);
            if (items.Count == 0) return;

            _logger.Information("Retry queue: draining {Count} item(s)", items.Count);

            foreach (var item in items)
            {
                ct.ThrowIfCancellationRequested();
                if (item.AttemptCount >= MaxAttempts)
                {
                    _logger.Warning(
                        "Retry queue: abandoning {Id} after {Attempts} attempts (lastError={LastError})",
                        item.Id, item.AttemptCount, item.LastError);
                    continue;
                }

                try
                {
                    item.AttemptCount++;
                    var response = await _client.FileMailAsync(item.Request, ct).ConfigureAwait(false);
                    await _queue.RemoveAsync(item.Id, ct).ConfigureAwait(false);
                    _logger.Information(
                        "Retry queue: filed {Id} (espoId={EspoId}, created={Created})",
                        item.Id, response?.Id, response?.Created);
                }
                catch (EspoCrmAuthorizationException ex)
                {
                    _logger.Warning(ex, "Retry queue: auth failure — pausing drain");
                    item.LastError = ex.Message;
                    await _queue.EnqueueAsync(item, ct).ConfigureAwait(false);
                    return;
                }
                catch (OperationCanceledException) { throw; }
                catch (Exception ex)
                {
                    _logger.Warning(ex, "Retry queue: item {Id} attempt {Attempt} failed", item.Id, item.AttemptCount);
                    item.LastError = ex.Message;
                    await _queue.EnqueueAsync(item, ct).ConfigureAwait(false);
                }
            }
        }

        public void Dispose()
        {
            try { _cts.Cancel(); } catch { }
            _cts.Dispose();
        }
    }
}
