using System;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Messerli.Email.Configuration;
using Messerli.Email.Time;
using Messerli.FileSystem;
using Messerli.IO;
using Moq;
using Xunit;
using Xunit.Abstractions;
using static Messerli.Email.Test.IntegrationTestUtility;

namespace Messerli.Email.Test
{
    public sealed class MailKitIntegrationTest
    {
        private const string MessageId = "message-id@localhost";

        private const string Subject = "Test";

        private const string AttachmentFileName = "art.jpg";

        private static readonly PickupDirectory PickupDirectory = new("dummy");

        private static readonly DateTimeOffset DummyDateTime = new(new DateTime(2020, 2, 20), TimeSpan.FromHours(1));

        private static readonly MailboxAddress FromAddress = new("from@localhost", "From");

        private static readonly MailboxAddress ToAddress = new("to@localhost");

        private static readonly MailboxAddress CcAddress = new("cc@localhost");

        private static readonly MailboxAddress BccAddress = new("bcc@localhost");

        private static readonly EmailMessageBuilder EmailMessageBuilderWithoutContent
            = new EmailMessageBuilder()
                .From(FromAddress)
                .Subject(Subject)
                .AddRecipient(ToAddress)
                .AddCarbonCopyRecipient(CcAddress)
                .AddBlindCarbonCopyRecipient(BccAddress);

        private readonly ITestOutputHelper _testOutputHelper;

        public MailKitIntegrationTest(ITestOutputHelper testOutputHelper)
        {
            _testOutputHelper = testOutputHelper;
        }

        [Theory]
        [MemberData(nameof(GetEmailMessagesData))]
        public async Task SendMessage_ValidEmailMessage_EmailIsWrittenToFile(string expectedMailName, EmailMessage message)
        {
            await using var stream = new MemoryStream();

            var mailKitWrapper = CreateEmailSender(stream);
            await mailKitWrapper.SendMessage(message);

            RewindStream(stream);
            var result = await ReadStreamToString(stream);
            _testOutputHelper.WriteLine(result);

            var expected = ReadExpectedEmail(expectedMailName);
            Assert.Equal(expected, result);
        }

        public static TheoryData<string, EmailMessage> GetEmailMessagesData()
            => new()
            {
                {
                    "AutoGeneratedEmail",
                    EmailMessageBuilderWithoutContent
                        .AutoGenerationType(AutoGenerationType.AutoGenerated)
                        .AddBodyPart(new BodyPart.Plain("Hello World"))
                        .Build()
                },
                {
                    "AutoRepliedEmail",
                    EmailMessageBuilderWithoutContent
                        .AutoGenerationType(AutoGenerationType.AutoReplied)
                        .AddBodyPart(new BodyPart.Plain("Hello World"))
                        .Build()
                },
                {
                    "EmailWithHtmlBody",
                    EmailMessageBuilderWithoutContent
                        .AddBodyPart(new BodyPart.Html("<h1>Hello World.</h1><p>this is a test.</p>"))
                        .Build()
                },
                {
                    "EmailWithAlternativeContent",
                    EmailMessageBuilderWithoutContent
                        .AddBodyPart(new BodyPart.Alternatives(
                            new BodyPart.Plain("Hello World. This is a test."),
                            new BodyPart.Html("<h1>Hello World.</h1><p>this is a test.</p>")))
                        .Build()
                },
                {
                    "EmailWithAttachment",
                    EmailMessageBuilderWithoutContent
                        .AddBodyPart(new BodyPart.Plain("This is art"))
                        .AddBodyPart(new BodyPart.Attachment(
                            new ContentType(MediaTypeNames.Image.Jpeg),
                            AttachmentFileName,
                            () => OpenResourceFile(AttachmentFileName)))
                        .Build()
                },
            };

        private static string ReadExpectedEmail(string name)
            => File.ReadAllText(Path.Combine("ExpectedEmail", name + ".eml"));

        private static async Task<string> ReadStreamToString(Stream stream)
        {
            using var streamReader = new StreamReader(stream);
            return await streamReader.ReadToEndAsync();
        }

        private static void RewindStream(Stream stream)
            => stream.Seek(0, SeekOrigin.Begin);

        private static IEmailSender CreateEmailSender(Stream stream)
            => new EmailSenderBuilder()
                .FileSystem(Mock.Of<IFileSystem>())
                .FileOpeningBuilder(MockFileOpeningBuilder(stream))
                .DateTimeAccessor(MockDateTimeAccessor())
                .MessageIdGenerator(MockMessageIdGenerator())
                .MultipartBoundaryGenerator(MockMultipartBoundaryGenerator())
                .Build(new EmailDelivery.PickupDelivery(PickupDirectory));

        private static IFileOpeningBuilder MockFileOpeningBuilder(Stream stream)
            => Mocks
                .Of<IFileOpeningBuilder>()
                .Where(f => f.Create(true) == f)
                .Where(f => f.Write(true) == f)
                .First(f => f.Open(It.IsAny<string>()) == stream.Borrow());

        private static IDateTimeAccessor MockDateTimeAccessor()
            => Mocks
                .Of<IDateTimeAccessor>()
                .First(d => d.Now == DummyDateTime);

        private static IMessageIdGenerator MockMessageIdGenerator()
            => Mocks
                .Of<IMessageIdGenerator>()
                .First(g => g.GenerateMessageId() == MessageId);

        private static IMultipartBoundaryGenerator MockMultipartBoundaryGenerator()
        {
            var mock = new Mock<IMultipartBoundaryGenerator>();
            mock.SetupSequence(g => g.GenerateBoundary())
                .Returns("=-aaaaaaaa")
                .Returns("=-bbbbbbbb")
                .Returns("=-cccccccc")
                .Returns("=-dddddddd");
            return mock.Object;
        }
    }
}
