using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using MarcusLaw.OutlookAddin.Core.Models;
using MarcusLaw.OutlookAddin.Core.Services;
using Moq;
using Serilog;
using Xunit;

namespace MarcusLaw.OutlookAddin.Tests.Services
{
    public class FilingServiceTests
    {
        private static readonly ILogger Logger = new LoggerConfiguration().CreateLogger();

        private static MailEnvelope MakeEnvelope(string token, string? messageId = null) => new MailEnvelope
        {
            Token = token,
            MessageId = messageId ?? "<" + token + "@server>",
            Subject = "RE: " + token,
            From = "lawyer@marcus-law.co.il",
            To = new List<string> { "client@example.com" },
            Body = "body of " + token,
            DateSent = DateTimeOffset.UtcNow
        };

        private static FilingTarget Target => new FilingTarget("Case", "case-123", "Smith v Jones");

        [Fact]
        public async Task FileAsync_HappyPath_AllItemsFiled()
        {
            var client = new Mock<IEspoCrmClient>(MockBehavior.Strict);
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync((FileEmailRequest req, CancellationToken _) => new FileEmailResponse
                {
                    Id = "email-" + req.MessageId,
                    Created = true,
                    ParentType = req.ParentType,
                    ParentId = req.ParentId,
                    MessageId = req.MessageId
                });
            var queue = new Mock<IRetryQueue>(MockBehavior.Strict);

            var svc = new FilingService(client.Object, queue.Object, Logger);
            var envelopes = new[] { MakeEnvelope("A"), MakeEnvelope("B") };

            var summary = await svc.FileAsync(envelopes, Target);

            summary.Items.Should().HaveCount(2);
            summary.FiledCount.Should().Be(2);
            summary.QueuedCount.Should().Be(0);
            summary.AuthRequired.Should().BeFalse();
            summary.Items[0].FiledEntityId.Should().Be("email-<A@server>");
            summary.Items[0].Token.Should().Be("A");
        }

        [Fact]
        public async Task FileAsync_409ConflictIsStillSuccess()
        {
            var client = new Mock<IEspoCrmClient>(MockBehavior.Strict);
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(new FileEmailResponse { Id = "existing", Created = false });
            var queue = new Mock<IRetryQueue>(MockBehavior.Strict);

            var svc = new FilingService(client.Object, queue.Object, Logger);
            var summary = await svc.FileAsync(new[] { MakeEnvelope("A") }, Target);

            summary.FiledCount.Should().Be(1);
            summary.Items[0].Outcome.Should().Be(FilingOutcome.Filed);
            summary.Items[0].FiledEntityId.Should().Be("existing");
        }

        [Fact]
        public async Task FileAsync_401_SetsAuthRequiredAndSkipsRemaining()
        {
            var client = new Mock<IEspoCrmClient>(MockBehavior.Strict);
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .ThrowsAsync(new EspoCrmAuthorizationException("API key rejected"));
            var queue = new Mock<IRetryQueue>(MockBehavior.Strict);

            var svc = new FilingService(client.Object, queue.Object, Logger);
            var summary = await svc.FileAsync(new[] { MakeEnvelope("A"), MakeEnvelope("B") }, Target);

            summary.AuthRequired.Should().BeTrue();
            summary.FiledCount.Should().Be(0);
            summary.Items.Should().AllSatisfy(i => i.Outcome.Should().Be(FilingOutcome.AuthRequired));
            client.Verify(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()), Times.Once);
        }

        [Fact]
        public async Task FileAsync_TransientFailure_EnqueuesToRetryQueue()
        {
            var client = new Mock<IEspoCrmClient>(MockBehavior.Strict);
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .ThrowsAsync(new System.Net.Http.HttpRequestException("connection reset"));

            var enqueued = new List<RetryQueueItem>();
            var queue = new Mock<IRetryQueue>(MockBehavior.Strict);
            queue.Setup(q => q.EnqueueAsync(It.IsAny<RetryQueueItem>(), It.IsAny<CancellationToken>()))
                .Callback<RetryQueueItem, CancellationToken>((it, _) => enqueued.Add(it))
                .ReturnsAsync("queue-id");

            var svc = new FilingService(client.Object, queue.Object, Logger);
            var summary = await svc.FileAsync(new[] { MakeEnvelope("A") }, Target);

            summary.FiledCount.Should().Be(0);
            summary.QueuedCount.Should().Be(1);
            summary.Items[0].Outcome.Should().Be(FilingOutcome.Queued);
            enqueued.Should().HaveCount(1);
            enqueued[0].Request.ParentType.Should().Be("Case");
            enqueued[0].Request.ParentId.Should().Be("case-123");
            enqueued[0].TargetName.Should().Be("Smith v Jones");
        }

        [Fact]
        public async Task FileAsync_QueueFailureFallsThroughToFailed()
        {
            var client = new Mock<IEspoCrmClient>(MockBehavior.Strict);
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .ThrowsAsync(new InvalidOperationException("primary"));
            var queue = new Mock<IRetryQueue>(MockBehavior.Strict);
            queue.Setup(q => q.EnqueueAsync(It.IsAny<RetryQueueItem>(), It.IsAny<CancellationToken>()))
                .ThrowsAsync(new System.IO.IOException("disk full"));

            var svc = new FilingService(client.Object, queue.Object, Logger);
            var summary = await svc.FileAsync(new[] { MakeEnvelope("A") }, Target);

            summary.FailedCount.Should().Be(1);
            summary.Items[0].Outcome.Should().Be(FilingOutcome.Failed);
            summary.Items[0].ErrorMessage.Should().Contain("primary").And.Contain("disk full");
        }

        [Fact]
        public async Task FileAsync_PassesParentTargetIntoRequest()
        {
            FileEmailRequest? captured = null;
            var client = new Mock<IEspoCrmClient>();
            client.Setup(c => c.FileMailAsync(It.IsAny<FileEmailRequest>(), It.IsAny<CancellationToken>()))
                .Callback<FileEmailRequest, CancellationToken>((req, _) => captured = req)
                .ReturnsAsync(new FileEmailResponse { Id = "x", Created = true });
            var queue = new Mock<IRetryQueue>();

            var svc = new FilingService(client.Object, queue.Object, Logger);
            await svc.FileAsync(new[] { MakeEnvelope("A") }, Target);

            captured.Should().NotBeNull();
            captured!.ParentType.Should().Be("Case");
            captured.ParentId.Should().Be("case-123");
            captured.MessageId.Should().Be("<A@server>");
        }

        [Fact]
        public async Task FileAsync_RejectsNullArguments()
        {
            var svc = new FilingService(Mock.Of<IEspoCrmClient>(), Mock.Of<IRetryQueue>(), Logger);
            await Assert.ThrowsAsync<ArgumentNullException>(() => svc.FileAsync(null!, Target));
            await Assert.ThrowsAsync<ArgumentNullException>(() => svc.FileAsync(Array.Empty<MailEnvelope>(), null!));
        }
    }
}
