using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MarcusLaw.OutlookAddin.Core.Models;
using Serilog;

namespace MarcusLaw.OutlookAddin.Core.Services
{
    public sealed class FilingService : IFilingService
    {
        private readonly IEspoCrmClient _client;
        private readonly IRetryQueue _retryQueue;
        private readonly ILogger _logger;

        public FilingService(IEspoCrmClient client, IRetryQueue retryQueue, ILogger logger)
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _retryQueue = retryQueue ?? throw new ArgumentNullException(nameof(retryQueue));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task<FilingBatchSummary> FileAsync(
            IReadOnlyList<MailEnvelope> envelopes,
            FilingTarget target,
            CancellationToken cancellationToken = default)
        {
            if (envelopes == null) throw new ArgumentNullException(nameof(envelopes));
            if (target == null) throw new ArgumentNullException(nameof(target));

            var items = new List<FilingItemResult>(envelopes.Count);
            var authBlocked = false;

            foreach (var env in envelopes)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (authBlocked)
                {
                    items.Add(new FilingItemResult
                    {
                        Token = env.Token,
                        MessageId = env.MessageId,
                        Outcome = FilingOutcome.AuthRequired,
                        ErrorMessage = "Skipped — earlier item rejected by EspoCRM"
                    });
                    continue;
                }

                var request = BuildRequest(env, target);

                try
                {
                    var response = await _client.FileMailAsync(request, cancellationToken).ConfigureAwait(false);
                    items.Add(new FilingItemResult
                    {
                        Token = env.Token,
                        MessageId = env.MessageId,
                        Outcome = FilingOutcome.Filed,
                        FiledEntityId = response?.Id
                    });
                    _logger.Information(
                        "Filed messageId={MessageId} to {ParentType}/{ParentId} (created={Created})",
                        env.MessageId, target.ParentType, target.ParentId, response?.Created);
                }
                catch (EspoCrmAuthorizationException ex)
                {
                    _logger.Warning(ex, "Filing aborted: EspoCRM rejected credentials");
                    authBlocked = true;
                    items.Add(new FilingItemResult
                    {
                        Token = env.Token,
                        MessageId = env.MessageId,
                        Outcome = FilingOutcome.AuthRequired,
                        ErrorMessage = ex.Message
                    });
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, "Filing failed for messageId={MessageId}, enqueueing for retry", env.MessageId);
                    items.Add(await EnqueueOrFailAsync(env, request, target, ex, cancellationToken).ConfigureAwait(false));
                }
            }

            return new FilingBatchSummary { Items = items };
        }

        private async Task<FilingItemResult> EnqueueOrFailAsync(
            MailEnvelope env,
            FileEmailRequest request,
            FilingTarget target,
            Exception primaryError,
            CancellationToken cancellationToken)
        {
            try
            {
                await _retryQueue.EnqueueAsync(new RetryQueueItem
                {
                    EnqueuedAt = DateTimeOffset.UtcNow,
                    Request = request,
                    TargetName = target.ParentName,
                    LastError = primaryError.Message
                }, cancellationToken).ConfigureAwait(false);

                return new FilingItemResult
                {
                    Token = env.Token,
                    MessageId = env.MessageId,
                    Outcome = FilingOutcome.Queued,
                    ErrorMessage = primaryError.Message
                };
            }
            catch (Exception queueEx)
            {
                _logger.Error(queueEx, "Retry queue enqueue failed for messageId={MessageId}", env.MessageId);
                return new FilingItemResult
                {
                    Token = env.Token,
                    MessageId = env.MessageId,
                    Outcome = FilingOutcome.Failed,
                    ErrorMessage = $"{primaryError.Message} | queue: {queueEx.Message}"
                };
            }
        }

        private static FileEmailRequest BuildRequest(MailEnvelope env, FilingTarget target)
        {
            return new FileEmailRequest
            {
                MessageId = env.MessageId,
                Subject = env.Subject,
                From = env.From,
                To = new List<string>(env.To),
                Cc = new List<string>(env.Cc),
                Bcc = new List<string>(env.Bcc),
                ReplyTo = new List<string>(env.ReplyTo),
                Body = env.Body,
                IsHtml = env.IsHtml,
                DateSent = env.DateSent,
                ParentType = target.ParentType,
                ParentId = target.ParentId,
                Attachments = new List<EspoAttachment>(env.Attachments)
            };
        }
    }
}
